diff --git a/CHANGELOG.md b/CHANGELOG.md
index 21eb4ab..7d3acef 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Create playlist from Library** — new `+` button in the Library header opens a modal to create an empty playlist directly from the app.
- **Auto-download** — optional setting (disabled by default) that automatically downloads each played track to the device for offline playback. Enabling shows a confirmation warning about storage and server bandwidth usage.
- **Add ext- tracks to playlist** — Deezer tracks can now be added to playlists via the add-to-playlist sheet; OctoFiesta import is triggered automatically and Navidrome is polled until the native ID is available.
+- **iOS app icon** — all required sizes now bundled (20×20 → 1024×1024) for iPhone and iPad; `Contents.json` updated with explicit filenames for every slot.
### Fixed
- **deezerFetch timeout** — all Deezer API calls now use an 8 s `AbortController` timeout, preventing Magic Shuffle and Autoplay from hanging silently on slow CDN responses.
diff --git a/ios/Musonic/Images.xcassets/AppIcon.appiconset/Contents.json b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Contents.json
index 359c1d6..92a5988 100644
--- a/ios/Musonic/Images.xcassets/AppIcon.appiconset/Contents.json
+++ b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Contents.json
@@ -1,53 +1,116 @@
-{
- "images" : [
- {
- "idiom" : "iphone",
- "scale" : "2x",
- "size" : "20x20"
- },
- {
- "idiom" : "iphone",
- "scale" : "3x",
- "size" : "20x20"
- },
- {
- "idiom" : "iphone",
- "scale" : "2x",
- "size" : "29x29"
- },
- {
- "idiom" : "iphone",
- "scale" : "3x",
- "size" : "29x29"
- },
- {
- "idiom" : "iphone",
- "scale" : "2x",
- "size" : "40x40"
- },
- {
- "idiom" : "iphone",
- "scale" : "3x",
- "size" : "40x40"
- },
- {
- "idiom" : "iphone",
- "scale" : "2x",
- "size" : "60x60"
- },
- {
- "idiom" : "iphone",
- "scale" : "3x",
- "size" : "60x60"
- },
- {
- "idiom" : "ios-marketing",
- "scale" : "1x",
- "size" : "1024x1024"
- }
- ],
- "info" : {
- "author" : "xcode",
- "version" : 1
- }
-}
+{
+ "images": [
+ {
+ "filename": "Icon-40.png",
+ "idiom": "iphone",
+ "scale": "2x",
+ "size": "20x20"
+ },
+ {
+ "filename": "Icon-60.png",
+ "idiom": "iphone",
+ "scale": "3x",
+ "size": "20x20"
+ },
+ {
+ "filename": "Icon-58.png",
+ "idiom": "iphone",
+ "scale": "2x",
+ "size": "29x29"
+ },
+ {
+ "filename": "Icon-87.png",
+ "idiom": "iphone",
+ "scale": "3x",
+ "size": "29x29"
+ },
+ {
+ "filename": "Icon-80.png",
+ "idiom": "iphone",
+ "scale": "2x",
+ "size": "40x40"
+ },
+ {
+ "filename": "Icon-120.png",
+ "idiom": "iphone",
+ "scale": "3x",
+ "size": "40x40"
+ },
+ {
+ "filename": "Icon-120.png",
+ "idiom": "iphone",
+ "scale": "2x",
+ "size": "60x60"
+ },
+ {
+ "filename": "Icon-180.png",
+ "idiom": "iphone",
+ "scale": "3x",
+ "size": "60x60"
+ },
+ {
+ "filename": "Icon-20.png",
+ "idiom": "ipad",
+ "scale": "1x",
+ "size": "20x20"
+ },
+ {
+ "filename": "Icon-40.png",
+ "idiom": "ipad",
+ "scale": "2x",
+ "size": "20x20"
+ },
+ {
+ "filename": "Icon-29.png",
+ "idiom": "ipad",
+ "scale": "1x",
+ "size": "29x29"
+ },
+ {
+ "filename": "Icon-58.png",
+ "idiom": "ipad",
+ "scale": "2x",
+ "size": "29x29"
+ },
+ {
+ "filename": "Icon-40.png",
+ "idiom": "ipad",
+ "scale": "1x",
+ "size": "40x40"
+ },
+ {
+ "filename": "Icon-80.png",
+ "idiom": "ipad",
+ "scale": "2x",
+ "size": "40x40"
+ },
+ {
+ "filename": "Icon-76.png",
+ "idiom": "ipad",
+ "scale": "1x",
+ "size": "76x76"
+ },
+ {
+ "filename": "Icon-152.png",
+ "idiom": "ipad",
+ "scale": "2x",
+ "size": "76x76"
+ },
+ {
+ "filename": "Icon-167.png",
+ "idiom": "ipad",
+ "scale": "2x",
+ "size": "83.5x83.5"
+ },
+ {
+ "filename": "Icon-1024.png",
+ "idiom": "ios-marketing",
+ "scale": "1x",
+ "size": "1024x1024"
+ }
+ ],
+ "info": {
+ "author": "xcode",
+ "version": 1
+ }
+}
\ No newline at end of file
diff --git a/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-1024.png b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-1024.png
new file mode 100644
index 0000000..42e22f4
Binary files /dev/null and b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-1024.png differ
diff --git a/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-120.png b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-120.png
new file mode 100644
index 0000000..360c482
Binary files /dev/null and b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-120.png differ
diff --git a/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-152.png b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-152.png
new file mode 100644
index 0000000..e4236f7
Binary files /dev/null and b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-152.png differ
diff --git a/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-167.png b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-167.png
new file mode 100644
index 0000000..38368b6
Binary files /dev/null and b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-167.png differ
diff --git a/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-180.png b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-180.png
new file mode 100644
index 0000000..031186a
Binary files /dev/null and b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-180.png differ
diff --git a/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-20.png b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-20.png
new file mode 100644
index 0000000..861ea94
Binary files /dev/null and b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-20.png differ
diff --git a/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-29.png b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-29.png
new file mode 100644
index 0000000..2ea2e5e
Binary files /dev/null and b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-29.png differ
diff --git a/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-40.png b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-40.png
new file mode 100644
index 0000000..8a93eda
Binary files /dev/null and b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-40.png differ
diff --git a/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-58.png b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-58.png
new file mode 100644
index 0000000..ec0942d
Binary files /dev/null and b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-58.png differ
diff --git a/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-60.png b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-60.png
new file mode 100644
index 0000000..709d9e2
Binary files /dev/null and b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-60.png differ
diff --git a/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-76.png b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-76.png
new file mode 100644
index 0000000..7766c81
Binary files /dev/null and b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-76.png differ
diff --git a/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-80.png b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-80.png
new file mode 100644
index 0000000..4b34143
Binary files /dev/null and b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-80.png differ
diff --git a/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-87.png b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-87.png
new file mode 100644
index 0000000..5ebfd9c
Binary files /dev/null and b/ios/Musonic/Images.xcassets/AppIcon.appiconset/Icon-87.png differ
diff --git a/ios/Musonic/Info.plist b/ios/Musonic/Info.plist
index 818f665..467cd65 100644
--- a/ios/Musonic/Info.plist
+++ b/ios/Musonic/Info.plist
@@ -24,6 +24,32 @@
????
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
+ CFBundleIconName
+ AppIcon
+ CFBundleIcons
+
+ CFBundlePrimaryIcon
+
+ CFBundleIconName
+ AppIcon
+ CFBundleIconFiles
+
+ AppIcon60x60
+
+
+
+ CFBundleIcons~ipad
+
+ CFBundlePrimaryIcon
+
+ CFBundleIconName
+ AppIcon
+ CFBundleIconFiles
+
+ AppIcon76x76
+
+
+
LSRequiresIPhoneOS
NSAppTransportSecurity
diff --git a/package-lock.json b/package-lock.json
index 47ae57c..dd30152 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "Musonic",
- "version": "1.0.0",
+ "version": "1.0.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "Musonic",
- "version": "1.0.0",
+ "version": "1.0.2",
"hasInstallScript": true,
"dependencies": {
"@d11/react-native-fast-image": "^8.13.0",
diff --git a/src/screens/PlaylistDetail/index.tsx b/src/screens/PlaylistDetail/index.tsx
index 0d28046..db919b9 100644
--- a/src/screens/PlaylistDetail/index.tsx
+++ b/src/screens/PlaylistDetail/index.tsx
@@ -379,6 +379,7 @@ type HeaderProps = {
coverArtId?: string;
localCoverUri?: string | null;
playlistName: string;
+ owner: string;
songCount: number;
totalDuration: number;
isShuffled: boolean;
@@ -405,6 +406,7 @@ function PlaylistHeader({
coverArtId,
localCoverUri,
playlistName,
+ owner,
songCount,
totalDuration,
isShuffled: _isShuffled,
@@ -695,6 +697,7 @@ export default function PlaylistDetailScreen() {
const {playlistId, autoEdit} = route.params;
const [playlistName, setPlaylistName] = useState('');
+ const [owner, setOwner] = useState('');
const [description, setDescription] = useState('');
const [songs, setSongs] = useState([]);
const [coverArtId, setCoverArtId] = useState();
@@ -728,6 +731,10 @@ export default function PlaylistDetailScreen() {
const setShuffleMode = usePlayerStore(s => s.setShuffleMode);
const togglePlay = usePlayerStore(s => s.togglePlay);
const isOfflineMode = useSettingsStore(s => s.isOfflineMode);
+ const activeServerUsername = useSettingsStore(s => {
+ const id = s.activeServerId;
+ return s.servers.find(srv => srv.id === id)?.username ?? '';
+ });
const downloads = useDownloadStore(s => s.downloads);
const t = useT();
@@ -771,6 +778,7 @@ export default function PlaylistDetailScreen() {
const offlineSongs = usePlaylistCacheStore.getState().getOfflineSongs(playlistId);
const {isDownloaded} = useDownloadStore.getState();
setPlaylistName(cachedPl?.name ?? '');
+ setOwner(cachedPl?.owner ?? activeServerUsername);
setCoverArtId(cachedPl?.coverArt);
setSongs(offlineSongs.filter(s => isDownloaded(String(s.id))));
setLoading(false);
@@ -779,6 +787,7 @@ export default function PlaylistDetailScreen() {
getPlaylist(playlistId)
.then(({playlist, songs: s}) => {
setPlaylistName(playlist.name);
+ setOwner(playlist.owner ?? activeServerUsername);
setDescription(playlist.comment ?? '');
setCoverArtId(playlist.coverArt);
setSongs(s);
@@ -1102,6 +1111,7 @@ export default function PlaylistDetailScreen() {
coverArtId={coverArtId}
localCoverUri={localCoverUri}
playlistName={playlistName}
+ owner={owner}
songCount={songs.length}
totalDuration={totalDuration}
isShuffled={isShuffled}
@@ -1166,7 +1176,7 @@ export default function PlaylistDetailScreen() {
),
// eslint-disable-next-line react-hooks/exhaustive-deps
[
- topBarH, coverArtId, localCoverUri, playlistName, songs, totalDuration,
+ topBarH, coverArtId, localCoverUri, playlistName, owner, songs, totalDuration,
isShuffled, shuffleMode, isThisPlaylistActive, isGlobalPlaying, loadingPlaylist,
query, handlePlay, handleStartEdit, handleOpenInfo, isOfflineMode, downloads,
],