diff --git a/Mac/Components/Browser/Album Detail/AlbumTracksDataSource.swift b/Mac/Components/Browser/Album Detail/AlbumTracksDataSource.swift
index 59b74d0..2589d02 100644
--- a/Mac/Components/Browser/Album Detail/AlbumTracksDataSource.swift
+++ b/Mac/Components/Browser/Album Detail/AlbumTracksDataSource.swift
@@ -9,21 +9,6 @@
import AppKit
class AlbumTracksDataSource: NSObject, NSTableViewDataSource {
- struct AlbumSongItem {
- let disc: String?
- let song: Song?
-
- init(song: Song) {
- self.disc = nil
- self.song = song
- }
-
- init(disc: String) {
- self.disc = disc
- self.song = nil
- }
- }
-
var albumSongs: [AlbumSongItem] = []
var showSongArtist: Bool = false
diff --git a/Mac/Components/Shared/Extensions/CGColor.swift b/Mac/Components/Shared/Extensions/CGColor.swift
index 3e508c5..27d627c 100644
--- a/Mac/Components/Shared/Extensions/CGColor.swift
+++ b/Mac/Components/Shared/Extensions/CGColor.swift
@@ -6,9 +6,9 @@
// Copyright © 2019 Dan Barber. All rights reserved.
//
-import AppKit
+import CoreGraphics
extension CGColor {
- static let albumBorderColorLight = NSColor.black.withAlphaComponent(0.15).cgColor
- static let albumBorderColorDark = NSColor.white.withAlphaComponent(0.15).cgColor
+ static let albumBorderColorLight = CGColor.black.copy(alpha: 0.15)
+ static let albumBorderColorDark = CGColor.white.copy(alpha: 0.15)
}
diff --git a/Mac/Components/Window/Base.lproj/Main.storyboard b/Mac/Components/Window/Base.lproj/Main.storyboard
index 4ab71d9..4448111 100644
--- a/Mac/Components/Window/Base.lproj/Main.storyboard
+++ b/Mac/Components/Window/Base.lproj/Main.storyboard
@@ -298,7 +298,7 @@
-
+
@@ -320,7 +320,7 @@
-
+
@@ -347,7 +347,7 @@
-
+
@@ -369,7 +369,7 @@
-
+
@@ -385,7 +385,7 @@
-
+
@@ -398,7 +398,7 @@
-
+
@@ -414,7 +414,7 @@
-
+
@@ -516,7 +516,7 @@
-
+
@@ -554,7 +554,7 @@
-
+
@@ -566,7 +566,7 @@
-
+
@@ -614,7 +614,7 @@
-
+
@@ -637,7 +637,7 @@
-
+
@@ -651,7 +651,7 @@
-
+
@@ -677,7 +677,7 @@
-
+
@@ -712,7 +712,7 @@
-
+
@@ -757,7 +757,7 @@
-
+
@@ -778,7 +778,7 @@
-
+
@@ -805,7 +805,7 @@
-
+
@@ -818,7 +818,7 @@
-
+
diff --git a/Persephone.xcodeproj/project.pbxproj b/Persephone.xcodeproj/project.pbxproj
index 63ff64e..5de1286 100644
--- a/Persephone.xcodeproj/project.pbxproj
+++ b/Persephone.xcodeproj/project.pbxproj
@@ -8,6 +8,8 @@
/* Begin PBXBuildFile section */
38BAC36B249CB1A7004BAEA4 /* AlbumDetailSongTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38BAC36A249CB1A6004BAEA4 /* AlbumDetailSongTitleView.swift */; };
+ E403E63E246F4C3900200F58 /* NowPlayingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E403E63C246F4C3900200F58 /* NowPlayingViewController.swift */; };
+ E403E63F246F4C3900200F58 /* NowPlayingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E403E63D246F4C3900200F58 /* NowPlayingViewController.xib */; };
E407861C2110CE6E006887B1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E407861B2110CE6E006887B1 /* AppDelegate.swift */; };
E40786202110CE70006887B1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E407861F2110CE70006887B1 /* Assets.xcassets */; };
E40786232110CE70006887B1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E40786212110CE70006887B1 /* Main.storyboard */; };
@@ -19,6 +21,25 @@
E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3C1220E134F0006D9BE /* AlbumViewController.swift */; };
E408D3CB220E341D0006D9BE /* AlbumViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = E408D3C9220E341D0006D9BE /* AlbumViewItem.xib */; };
E40FE71B221B904300A4223F /* NSEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40FE71A221B904300A4223F /* NSEvent.swift */; };
+ E41222112431425400473C1D /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41221FD2431425400473C1D /* App.swift */; };
+ E41222122431425400473C1D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41221FE2431425400473C1D /* AppDelegate.swift */; };
+ E41222132431425400473C1D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E41221FF2431425400473C1D /* LaunchScreen.storyboard */; };
+ E41222142431425400473C1D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E41222012431425400473C1D /* Main.storyboard */; };
+ E41222162431425400473C1D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E41222042431425400473C1D /* Assets.xcassets */; };
+ E41222172431425400473C1D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41222052431425400473C1D /* SceneDelegate.swift */; };
+ E41222182431425400473C1D /* AlbumItemCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41222092431425400473C1D /* AlbumItemCell.swift */; };
+ E41222192431425400473C1D /* AlbumViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E412220A2431425400473C1D /* AlbumViewController.swift */; };
+ E412221A2431425400473C1D /* AlbumDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E412220B2431425400473C1D /* AlbumDataSource.swift */; };
+ E412221B2431425400473C1D /* AlbumViewController+UICollectionViewDelegateFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E412220C2431425400473C1D /* AlbumViewController+UICollectionViewDelegateFlowLayout.swift */; };
+ E412221C2431431500473C1D /* Persephone_iOSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41221F72431425300473C1D /* Persephone_iOSTests.swift */; };
+ E412221F2431432100473C1D /* Persephone_iOSUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41221FB2431425300473C1D /* Persephone_iOSUITests.swift */; };
+ E41222232431530900473C1D /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = E41222222431530900473C1D /* Kingfisher */; };
+ E41222242431535E00473C1D /* MPDAlbumArtImageDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BB7F8E23E5E7BC00906E2F /* MPDAlbumArtImageDataProvider.swift */; };
+ E4122228243153B200473C1D /* CGSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41222272431539800473C1D /* CGSize.swift */; };
+ E41222292431555100473C1D /* CGColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4928E0A2218D62A001D4BEA /* CGColor.swift */; };
+ E41222302432B0A300473C1D /* AlbumTracksDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E412222F2432B0A300473C1D /* AlbumTracksDataSource.swift */; };
+ E41222322432B14000473C1D /* AlbumSongItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41222312432B14000473C1D /* AlbumSongItem.swift */; };
+ E41222332432B15100473C1D /* AlbumSongItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41222312432B14000473C1D /* AlbumSongItem.swift */; };
E419E2872249B96600216A8C /* Song.swift in Sources */ = {isa = PBXBuildFile; fileRef = E419E2862249B96600216A8C /* Song.swift */; };
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41B22C521FB932700D544F6 /* MPDClient.swift */; };
E41E52FD223BF87300173814 /* MPDClient+Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E52FC223BF87300173814 /* MPDClient+Connection.swift */; };
@@ -250,7 +271,6 @@
E489E39D22B9CF0000CA8CBD /* NSView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E489E39C22B9CF0000CA8CBD /* NSView.swift */; };
E489E3A422B9D31800CA8CBD /* DraggedSongView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E489E3A222B9D31800CA8CBD /* DraggedSongView.swift */; };
E489E3A522B9D31800CA8CBD /* DraggedSongView.xib in Resources */ = {isa = PBXBuildFile; fileRef = E489E3A322B9D31800CA8CBD /* DraggedSongView.xib */; };
- E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4928E0A2218D62A001D4BEA /* CGColor.swift */; };
E49A5482233E580800EED353 /* PromiseKit in Frameworks */ = {isa = PBXBuildFile; productRef = E49A5481233E580800EED353 /* PromiseKit */; };
E49A5485233E5ADC00EED353 /* Differ in Frameworks */ = {isa = PBXBuildFile; productRef = E49A5484233E5ADC00EED353 /* Differ */; };
E49A5488233E5B0000EED353 /* ReSwift in Frameworks */ = {isa = PBXBuildFile; productRef = E49A5487233E5B0000EED353 /* ReSwift */; };
@@ -275,11 +295,17 @@
E4B11BBE2275EDAA0075461B /* PlayerActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BBD2275EDAA0075461B /* PlayerActions.swift */; };
E4B11BC02275EE150075461B /* QueueActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BBF2275EE150075461B /* QueueActions.swift */; };
E4B11BC22275EE410075461B /* AlbumListActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BC12275EE410075461B /* AlbumListActions.swift */; };
+ E4B28EE02436D56E003B28AE /* AlbumDetailFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B28EDE2436D548003B28AE /* AlbumDetailFooterView.swift */; };
+ E4B28EE224379606003B28AE /* CGColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B28EE124379606003B28AE /* CGColor.swift */; };
+ E4B3B3642432DB7A007E25D2 /* AlbumSongCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B3B3622432DA4B007E25D2 /* AlbumSongCell.swift */; };
+ E4B3B3672432DF1B007E25D2 /* AlbumSongListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B3B3652432DEDB007E25D2 /* AlbumSongListViewController.swift */; };
E4B3DF6523D66A4400728F6B /* QueueSongCoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B3DF6423D66A4400728F6B /* QueueSongCoverView.swift */; };
E4B46F8F2402E89800152157 /* MPDError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B46F8E2402E89800152157 /* MPDError.swift */; };
E4B5AE7E22F4C49600CCEC65 /* MPDServerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B5AE7D22F4C49600CCEC65 /* MPDServerDelegate.swift */; };
E4BB7F8F23E5E7BC00906E2F /* MPDAlbumArtImageDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BB7F8E23E5E7BC00906E2F /* MPDAlbumArtImageDataProvider.swift */; };
E4BB7F9323E9150A00906E2F /* CoverArtService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4BB7F9223E9150A00906E2F /* CoverArtService.swift */; };
+ E4C51F08243422380093FB31 /* AlbumDiscCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C51F07243422380093FB31 /* AlbumDiscCell.swift */; };
+ E4C51F0A243428B60093FB31 /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C51F09243428B60093FB31 /* UIFont.swift */; };
E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */; };
E4C8B53E22349002009A20F3 /* MPDIdle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53D22349002009A20F3 /* MPDIdle.swift */; };
E4D3BFA622B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4D3BFA522B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift */; };
@@ -296,6 +322,9 @@
E4F2EFEE24076A2700198159 /* ServerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F2EFED24076A2700198159 /* ServerState.swift */; };
E4F2EFF024076B0900198159 /* ServerActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F2EFEF24076B0900198159 /* ServerActions.swift */; };
E4F2EFF224076B5E00198159 /* ServerReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F2EFF124076B5E00198159 /* ServerReducer.swift */; };
+ E4F365D224942CC7006A8C4A /* NowPlayingTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F365D024942C6A006A8C4A /* NowPlayingTabBarController.swift */; };
+ E4F365D4249432C0006A8C4A /* NowPlayingTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F365D3249432C0006A8C4A /* NowPlayingTabBar.swift */; };
+ E4F365D6249C4CDC006A8C4A /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F365D5249C4CDC006A8C4A /* UIImage.swift */; };
E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */; };
E4F6B463221E125900ACF42A /* QueueItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B462221E125900ACF42A /* QueueItem.swift */; };
E4F6B467221E233200ACF42A /* AlbumDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B466221E233200ACF42A /* AlbumDataSource.swift */; };
@@ -370,6 +399,8 @@
/* Begin PBXFileReference section */
38BAC36A249CB1A6004BAEA4 /* AlbumDetailSongTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDetailSongTitleView.swift; sourceTree = ""; };
+ E403E63C246F4C3900200F58 /* NowPlayingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingViewController.swift; sourceTree = ""; };
+ E403E63D246F4C3900200F58 /* NowPlayingViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NowPlayingViewController.xib; sourceTree = ""; };
E40786182110CE6E006887B1 /* Persephone.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Persephone.app; sourceTree = BUILT_PRODUCTS_DIR; };
E407861B2110CE6E006887B1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
E407861F2110CE70006887B1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
@@ -391,6 +422,24 @@
E411C26D241C10F0008B9682 /* Persephone.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Persephone.app; sourceTree = BUILT_PRODUCTS_DIR; };
E411C282241C10F5008B9682 /* Persephone-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Persephone-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
E411C28D241C10F5008B9682 /* Persephone-iOSUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Persephone-iOSUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
+ E41221F72431425300473C1D /* Persephone_iOSTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persephone_iOSTests.swift; sourceTree = ""; };
+ E41221F82431425300473C1D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ E41221FA2431425300473C1D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ E41221FB2431425300473C1D /* Persephone_iOSUITests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Persephone_iOSUITests.swift; sourceTree = ""; };
+ E41221FD2431425400473C1D /* App.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; };
+ E41221FE2431425400473C1D /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ E41222002431425400473C1D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ E41222022431425400473C1D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ E41222032431425400473C1D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ E41222042431425400473C1D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ E41222052431425400473C1D /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
+ E41222092431425400473C1D /* AlbumItemCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumItemCell.swift; sourceTree = ""; };
+ E412220A2431425400473C1D /* AlbumViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumViewController.swift; sourceTree = ""; };
+ E412220B2431425400473C1D /* AlbumDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumDataSource.swift; sourceTree = ""; };
+ E412220C2431425400473C1D /* AlbumViewController+UICollectionViewDelegateFlowLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AlbumViewController+UICollectionViewDelegateFlowLayout.swift"; sourceTree = ""; };
+ E41222272431539800473C1D /* CGSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGSize.swift; sourceTree = ""; };
+ E412222F2432B0A300473C1D /* AlbumTracksDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumTracksDataSource.swift; sourceTree = ""; };
+ E41222312432B14000473C1D /* AlbumSongItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumSongItem.swift; sourceTree = ""; };
E419E2862249B96600216A8C /* Song.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Song.swift; sourceTree = ""; };
E41B22C421FB715A00D544F6 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; };
E41B22C521FB932700D544F6 /* MPDClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDClient.swift; sourceTree = ""; };
@@ -613,11 +662,17 @@
E4B11BBD2275EDAA0075461B /* PlayerActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerActions.swift; sourceTree = ""; };
E4B11BBF2275EE150075461B /* QueueActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueActions.swift; sourceTree = ""; };
E4B11BC12275EE410075461B /* AlbumListActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumListActions.swift; sourceTree = ""; };
+ E4B28EDE2436D548003B28AE /* AlbumDetailFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDetailFooterView.swift; sourceTree = ""; };
+ E4B28EE124379606003B28AE /* CGColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGColor.swift; sourceTree = ""; };
+ E4B3B3622432DA4B007E25D2 /* AlbumSongCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumSongCell.swift; sourceTree = ""; };
+ E4B3B3652432DEDB007E25D2 /* AlbumSongListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumSongListViewController.swift; sourceTree = ""; };
E4B3DF6423D66A4400728F6B /* QueueSongCoverView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueSongCoverView.swift; sourceTree = ""; };
E4B46F8E2402E89800152157 /* MPDError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDError.swift; sourceTree = ""; };
E4B5AE7D22F4C49600CCEC65 /* MPDServerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDServerDelegate.swift; sourceTree = ""; };
E4BB7F8E23E5E7BC00906E2F /* MPDAlbumArtImageDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDAlbumArtImageDataProvider.swift; sourceTree = ""; };
E4BB7F9223E9150A00906E2F /* CoverArtService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverArtService.swift; sourceTree = ""; };
+ E4C51F07243422380093FB31 /* AlbumDiscCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDiscCell.swift; sourceTree = ""; };
+ E4C51F09243428B60093FB31 /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = ""; };
E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = ""; };
E4C8B53D22349002009A20F3 /* MPDIdle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDIdle.swift; sourceTree = ""; };
E4D3BFA522B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "QueueViewController+NSOutlineViewDelegate.swift"; sourceTree = ""; };
@@ -635,6 +690,9 @@
E4F2EFED24076A2700198159 /* ServerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerState.swift; sourceTree = ""; };
E4F2EFEF24076B0900198159 /* ServerActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerActions.swift; sourceTree = ""; };
E4F2EFF124076B5E00198159 /* ServerReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerReducer.swift; sourceTree = ""; };
+ E4F365D024942C6A006A8C4A /* NowPlayingTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingTabBarController.swift; sourceTree = ""; };
+ E4F365D3249432C0006A8C4A /* NowPlayingTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingTabBar.swift; sourceTree = ""; };
+ E4F365D5249C4CDC006A8C4A /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = ""; };
E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueDataSource.swift; sourceTree = ""; };
E4F6B462221E125900ACF42A /* QueueItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueItem.swift; sourceTree = ""; };
E4F6B466221E233200ACF42A /* AlbumDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDataSource.swift; sourceTree = ""; };
@@ -678,6 +736,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ E41222232431530900473C1D /* Kingfisher in Frameworks */,
E480513824255CE000362CF3 /* ReSwift in Frameworks */,
E480513A24255CF200362CF3 /* CryptoSwift in Frameworks */,
);
@@ -705,6 +764,9 @@
children = (
E45E4FD822515D87004B537F /* Brewfile */,
E42A8F3922176D6400A13ED9 /* LICENSE.md */,
+ E41221FC2431425300473C1D /* iOS */,
+ E41221F62431425300473C1D /* iOSTests */,
+ E41221F92431425300473C1D /* iOSUITests */,
E407861A2110CE6E006887B1 /* Mac */,
E407862D2110CE70006887B1 /* MacTests */,
E40786382110CE70006887B1 /* MacUITests */,
@@ -765,15 +827,15 @@
E408D3B7220DE8CC0006D9BE /* Extensions */ = {
isa = PBXGroup;
children = (
- E4928E0A2218D62A001D4BEA /* CGColor.swift */,
+ E4B28EE124379606003B28AE /* CGColor.swift */,
E40FE71A221B904300A4223F /* NSEvent.swift */,
E435E3E1221CD4E200184CFC /* NSFont.swift */,
E435E3E3221CD75D00184CFC /* NSImage.swift */,
- E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */,
E489E39822B85D0400CA8CBD /* NSPasteboard.swift */,
- E489E39C22B9CF0000CA8CBD /* NSView.swift */,
E43AC1F022C68E6A001E483C /* NSPasteboardItem.swift */,
E4DA820523D6236200C1EE58 /* NSSize.swift */,
+ E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */,
+ E489E39C22B9CF0000CA8CBD /* NSView.swift */,
);
path = Extensions;
sourceTree = "";
@@ -804,6 +866,7 @@
E411C268241C02B2008B9682 /* Shared */ = {
isa = PBXGroup;
children = (
+ E4BB7F8D23E5E7A300906E2F /* ImageDataProviders */,
E483CE67242FEF82001F742E /* Delegates */,
E48056C22426D73300362CF3 /* libmpdclient */,
E48051512425607100362CF3 /* Controllers */,
@@ -825,6 +888,100 @@
path = Extensions;
sourceTree = "";
};
+ E41221F62431425300473C1D /* iOSTests */ = {
+ isa = PBXGroup;
+ children = (
+ E41221F82431425300473C1D /* Info.plist */,
+ E41221F72431425300473C1D /* Persephone_iOSTests.swift */,
+ );
+ path = iOSTests;
+ sourceTree = "";
+ };
+ E41221F92431425300473C1D /* iOSUITests */ = {
+ isa = PBXGroup;
+ children = (
+ E41221FA2431425300473C1D /* Info.plist */,
+ E41221FB2431425300473C1D /* Persephone_iOSUITests.swift */,
+ );
+ path = iOSUITests;
+ sourceTree = "";
+ };
+ E41221FC2431425300473C1D /* iOS */ = {
+ isa = PBXGroup;
+ children = (
+ E41221FD2431425400473C1D /* App.swift */,
+ E41221FE2431425400473C1D /* AppDelegate.swift */,
+ E41222042431425400473C1D /* Assets.xcassets */,
+ E41222062431425400473C1D /* Components */,
+ E41222032431425400473C1D /* Info.plist */,
+ E41221FF2431425400473C1D /* LaunchScreen.storyboard */,
+ E41222012431425400473C1D /* Main.storyboard */,
+ E41222052431425400473C1D /* SceneDelegate.swift */,
+ );
+ path = iOS;
+ sourceTree = "";
+ };
+ E41222062431425400473C1D /* Components */ = {
+ isa = PBXGroup;
+ children = (
+ E4D4FAE6246F4B1B00CD02AF /* Now Playing */,
+ E41222252431538700473C1D /* Shared */,
+ E41222072431425400473C1D /* Browser */,
+ );
+ path = Components;
+ sourceTree = "";
+ };
+ E41222072431425400473C1D /* Browser */ = {
+ isa = PBXGroup;
+ children = (
+ E412222A243182F700473C1D /* Album Detail */,
+ E41222082431425400473C1D /* Album Browser */,
+ );
+ path = Browser;
+ sourceTree = "";
+ };
+ E41222082431425400473C1D /* Album Browser */ = {
+ isa = PBXGroup;
+ children = (
+ E41222092431425400473C1D /* AlbumItemCell.swift */,
+ E412220A2431425400473C1D /* AlbumViewController.swift */,
+ E412220B2431425400473C1D /* AlbumDataSource.swift */,
+ E412220C2431425400473C1D /* AlbumViewController+UICollectionViewDelegateFlowLayout.swift */,
+ );
+ path = "Album Browser";
+ sourceTree = "";
+ };
+ E41222252431538700473C1D /* Shared */ = {
+ isa = PBXGroup;
+ children = (
+ E41222262431539000473C1D /* Extensions */,
+ );
+ path = Shared;
+ sourceTree = "";
+ };
+ E41222262431539000473C1D /* Extensions */ = {
+ isa = PBXGroup;
+ children = (
+ E4928E0A2218D62A001D4BEA /* CGColor.swift */,
+ E41222272431539800473C1D /* CGSize.swift */,
+ E4C51F09243428B60093FB31 /* UIFont.swift */,
+ E4F365D5249C4CDC006A8C4A /* UIImage.swift */,
+ );
+ path = Extensions;
+ sourceTree = "";
+ };
+ E412222A243182F700473C1D /* Album Detail */ = {
+ isa = PBXGroup;
+ children = (
+ E4C51F07243422380093FB31 /* AlbumDiscCell.swift */,
+ E4B3B3622432DA4B007E25D2 /* AlbumSongCell.swift */,
+ E4B3B3652432DEDB007E25D2 /* AlbumSongListViewController.swift */,
+ E412222F2432B0A300473C1D /* AlbumTracksDataSource.swift */,
+ E4B28EDE2436D548003B28AE /* AlbumDetailFooterView.swift */,
+ );
+ path = "Album Detail";
+ sourceTree = "";
+ };
E41B22C721FB966C00D544F6 /* include */ = {
isa = PBXGroup;
children = (
@@ -929,10 +1086,10 @@
E442CCC82347D65300004E0C /* Album Detail */ = {
isa = PBXGroup;
children = (
- E43B67A922909793007DCF55 /* AlbumDetailView.xib */,
E4A3A6A022A457B600EA2C40 /* AlbumDetailSongListView.swift */,
E45878372296173C00586A1C /* AlbumDetailSongRowView.swift */,
E43B67A822909793007DCF55 /* AlbumDetailView.swift */,
+ E43B67A922909793007DCF55 /* AlbumDetailView.xib */,
E4E7A6AC22AAAF98006D566C /* AlbumDetailView+NSTableViewDelegate.swift */,
E43B67AC229194CD007DCF55 /* AlbumTracksDataSource.swift */,
38BAC36A249CB1A6004BAEA4 /* AlbumDetailSongTitleView.swift */,
@@ -943,7 +1100,6 @@
E442CCC92347D6FD00004E0C /* Shared */ = {
isa = PBXGroup;
children = (
- E4BB7F8D23E5E7A300906E2F /* ImageDataProviders */,
E4E13C2C2350D8CB00092A6E /* Layouts */,
E408D3B7220DE8CC0006D9BE /* Extensions */,
E489E3A222B9D31800CA8CBD /* DraggedSongView.swift */,
@@ -1274,6 +1430,17 @@
path = Protocols;
sourceTree = "";
};
+ E4D4FAE6246F4B1B00CD02AF /* Now Playing */ = {
+ isa = PBXGroup;
+ children = (
+ E403E63C246F4C3900200F58 /* NowPlayingViewController.swift */,
+ E403E63D246F4C3900200F58 /* NowPlayingViewController.xib */,
+ E4F365D024942C6A006A8C4A /* NowPlayingTabBarController.swift */,
+ E4F365D3249432C0006A8C4A /* NowPlayingTabBar.swift */,
+ );
+ path = "Now Playing";
+ sourceTree = "";
+ };
E4E13C2C2350D8CB00092A6E /* Layouts */ = {
isa = PBXGroup;
children = (
@@ -1286,6 +1453,7 @@
isa = PBXGroup;
children = (
E450AD7D222620A10091BED3 /* Album.swift */,
+ E41222312432B14000473C1D /* AlbumSongItem.swift */,
E43AC1F422C6A4F4001E483C /* DraggedAlbum.swift */,
E451E36C22BD23DB008BE9B2 /* DraggedSong.swift */,
E451E36A22BD214D008BE9B2 /* DraggedSongType.swift */,
@@ -1385,6 +1553,7 @@
packageProductDependencies = (
E480513724255CE000362CF3 /* ReSwift */,
E480513924255CF200362CF3 /* CryptoSwift */,
+ E41222222431530900473C1D /* Kingfisher */,
);
productName = "Persephone-iOS";
productReference = E411C26D241C10F0008B9682 /* Persephone.app */;
@@ -1546,12 +1715,16 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ E41222132431425400473C1D /* LaunchScreen.storyboard in Resources */,
+ E41222142431425400473C1D /* Main.storyboard in Resources */,
E48059112426D73600362CF3 /* win64.txt in Resources */,
E480590F2426D73600362CF3 /* configure.py in Resources */,
E48058292426D73500362CF3 /* version.h.in in Resources */,
E48059152426D73600362CF3 /* build.sh in Resources */,
+ E403E63F246F4C3900200F58 /* NowPlayingViewController.xib in Resources */,
E48059132426D73600362CF3 /* win32.txt in Resources */,
E48059C12426D73600362CF3 /* libmpdclient.vapi in Resources */,
+ E41222162431425400473C1D /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1601,6 +1774,7 @@
E48059EE2426D73600362CF3 /* socket.c in Sources */,
E4F6B467221E233200ACF42A /* AlbumDataSource.swift in Sources */,
E4B11BA92274EDE30075461B /* Loading.swift in Sources */,
+ E41222332432B15100473C1D /* AlbumSongItem.swift in Sources */,
E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */,
E42A4D4F22E20D7D001C6CAD /* MPDTag.swift in Sources */,
E48059D82426D73600362CF3 /* database.c in Sources */,
@@ -1609,7 +1783,6 @@
E4B11BB62275374B0075461B /* UserNotificationsController.swift in Sources */,
E43AC1F622C6AD0B001E483C /* AlbumViewController+NSCollectionViewDelegate.swift in Sources */,
E4B11B68226A4FA00075461B /* QueueState.swift in Sources */,
- E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */,
E4D3BFA622B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift in Sources */,
E4405192227644340090CD6F /* MPDServerController.swift in Sources */,
E4805A102426D73600362CF3 /* tag.c in Sources */,
@@ -1742,6 +1915,7 @@
E48059FC2426D73600362CF3 /* neighbor.c in Sources */,
E4B11B6A226A4FBC0075461B /* AlbumListState.swift in Sources */,
E4805A162426D73600362CF3 /* song.c in Sources */,
+ E4B28EE224379606003B28AE /* CGColor.swift in Sources */,
E41E5305223BFB0700173814 /* MPDClient+Error.swift in Sources */,
E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */,
E4805A0C2426D73600362CF3 /* settings.c in Sources */,
@@ -1782,17 +1956,20 @@
E48059EB2426D73600362CF3 /* coutput.c in Sources */,
E48059FB2426D73600362CF3 /* recv.c in Sources */,
E480511A24255BAF00362CF3 /* Time.swift in Sources */,
+ E41222112431425400473C1D /* App.swift in Sources */,
E480514C24255E7D00362CF3 /* QueueActions.swift in Sources */,
E480511E24255BD500362CF3 /* RawRepresentable.swift in Sources */,
E4805A1D2426D73600362CF3 /* kvlist.c in Sources */,
E4805A2B2426D73600362CF3 /* message.c in Sources */,
E48059C92426D73600362CF3 /* async.c in Sources */,
+ E4122228243153B200473C1D /* CGSize.swift in Sources */,
E480514924255E7D00362CF3 /* AlbumListActions.swift in Sources */,
E480511624255BAF00362CF3 /* Loading.swift in Sources */,
E480514324255E7700362CF3 /* AppReducer.swift in Sources */,
E4805A292426D73600362CF3 /* player.c in Sources */,
E480514724255E7700362CF3 /* ServerReducer.swift in Sources */,
E480514E24255E7D00362CF3 /* UIActions.swift in Sources */,
+ E4B28EE02436D56E003B28AE /* AlbumDetailFooterView.swift in Sources */,
E480513C24255E7200362CF3 /* AppState.swift in Sources */,
E480514024255E7200362CF3 /* ServerState.swift in Sources */,
E480514224255E7700362CF3 /* AlbumListReducer.swift in Sources */,
@@ -1802,31 +1979,42 @@
E480512724255BDB00362CF3 /* MPDClient+Queue.swift in Sources */,
E480513D24255E7200362CF3 /* PlayerState.swift in Sources */,
E480511D24255BD200362CF3 /* MPDClientWrapper.c in Sources */,
+ E41222242431535E00473C1D /* MPDAlbumArtImageDataProvider.swift in Sources */,
E480512924255BDB00362CF3 /* MPDClient+Status.swift in Sources */,
E48059FD2426D73600362CF3 /* neighbor.c in Sources */,
E4805A252426D73600362CF3 /* iso8601.c in Sources */,
E480512C24255BDF00362CF3 /* MPDCommand.swift in Sources */,
E48059F52426D73600362CF3 /* search.c in Sources */,
E4805A1F2426D73600362CF3 /* idle.c in Sources */,
+ E4F365D224942CC7006A8C4A /* NowPlayingTabBarController.swift in Sources */,
+ E41222182431425400473C1D /* AlbumItemCell.swift in Sources */,
+ E4F365D4249432C0006A8C4A /* NowPlayingTabBar.swift in Sources */,
E480511724255BAF00362CF3 /* MPDServer.swift in Sources */,
E48059C32426D73600362CF3 /* directory.c in Sources */,
E480514A24255E7D00362CF3 /* PlayerActions.swift in Sources */,
E48059DF2426D73600362CF3 /* audio_format.c in Sources */,
E48059CF2426D73600362CF3 /* cpartition.c in Sources */,
+ E412221B2431425400473C1D /* AlbumViewController+UICollectionViewDelegateFlowLayout.swift in Sources */,
E4805A012426D73600362CF3 /* quote.c in Sources */,
E4805A092426D73600362CF3 /* fd_util.c in Sources */,
E48059D52426D73600362CF3 /* resolver.c in Sources */,
E48059D72426D73600362CF3 /* ierror.c in Sources */,
E48059F32426D73600362CF3 /* capabilities.c in Sources */,
E483CE69242FEFB8001F742E /* Notification.swift in Sources */,
+ E41222322432B14000473C1D /* AlbumSongItem.swift in Sources */,
E480511B24255BAF00362CF3 /* TrackTimer.swift in Sources */,
E480513124255BDF00362CF3 /* MPDStatus.swift in Sources */,
E480514424255E7700362CF3 /* PlayerReducer.swift in Sources */,
+ E4B3B3672432DF1B007E25D2 /* AlbumSongListViewController.swift in Sources */,
E480514524255E7700362CF3 /* PreferencesReducer.swift in Sources */,
+ E41222292431555100473C1D /* CGColor.swift in Sources */,
+ E4F365D6249C4CDC006A8C4A /* UIImage.swift in Sources */,
E480511F24255BDB00362CF3 /* MPDClient+Album.swift in Sources */,
E4805A0B2426D73600362CF3 /* sync.c in Sources */,
E4805A052426D73600362CF3 /* connection.c in Sources */,
+ E403E63E246F4C3900200F58 /* NowPlayingViewController.swift in Sources */,
E480512324255BDB00362CF3 /* MPDClient+Database.swift in Sources */,
+ E41222302432B0A300473C1D /* AlbumTracksDataSource.swift in Sources */,
E48059ED2426D73600362CF3 /* stats.c in Sources */,
E480514824255E7700362CF3 /* UIReducer.swift in Sources */,
E480512124255BDB00362CF3 /* MPDClient+Command.swift in Sources */,
@@ -1841,11 +2029,16 @@
E480513E24255E7200362CF3 /* PreferencesState.swift in Sources */,
E4805A132426D73600362CF3 /* rplaylist.c in Sources */,
E480513024255BDF00362CF3 /* MPDSong.swift in Sources */,
+ E4B3B3642432DB7A007E25D2 /* AlbumSongCell.swift in Sources */,
+ E4C51F0A243428B60093FB31 /* UIFont.swift in Sources */,
E480511524255BAF00362CF3 /* DraggedSongType.swift in Sources */,
E480512B24255BDF00362CF3 /* MPDAlbum.swift in Sources */,
+ E412221A2431425400473C1D /* AlbumDataSource.swift in Sources */,
+ E41222172431425400473C1D /* SceneDelegate.swift in Sources */,
E48059DB2426D73600362CF3 /* entity.c in Sources */,
E48051522425626300362CF3 /* MPDServerController.swift in Sources */,
E480511324255BAF00362CF3 /* DraggedAlbum.swift in Sources */,
+ E41222122431425400473C1D /* AppDelegate.swift in Sources */,
E4805A072426D73600362CF3 /* error.c in Sources */,
E480514124255E7200362CF3 /* UIState.swift in Sources */,
E48059E72426D73600362CF3 /* playlist.c in Sources */,
@@ -1855,9 +2048,11 @@
E4805A1B2426D73600362CF3 /* cmessage.c in Sources */,
E48059E52426D73600362CF3 /* run.c in Sources */,
E480514D24255E7D00362CF3 /* ServerActions.swift in Sources */,
+ E4C51F08243422380093FB31 /* AlbumDiscCell.swift in Sources */,
E480511C24255BBF00362CF3 /* MPDClient.swift in Sources */,
E480513B24255E7200362CF3 /* AlbumListState.swift in Sources */,
E480512224255BDB00362CF3 /* MPDClient+Connection.swift in Sources */,
+ E41222192431425400473C1D /* AlbumViewController.swift in Sources */,
E480511124255BA900362CF3 /* MachTime.swift in Sources */,
E4805A112426D73600362CF3 /* tag.c in Sources */,
E4805A192426D73600362CF3 /* status.c in Sources */,
@@ -1888,6 +2083,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ E412221C2431431500473C1D /* Persephone_iOSTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1895,6 +2091,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ E412221F2431432100473C1D /* Persephone_iOSUITests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1932,6 +2129,22 @@
name = Main.storyboard;
sourceTree = "";
};
+ E41221FF2431425400473C1D /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ E41222002431425400473C1D /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+ E41222012431425400473C1D /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ E41222022431425400473C1D /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
@@ -2527,6 +2740,11 @@
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
+ E41222222431530900473C1D /* Kingfisher */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = E43BEC9E238835DC00CAF1EB /* XCRemoteSwiftPackageReference "Kingfisher" */;
+ productName = Kingfisher;
+ };
E43BEC9F238835DC00CAF1EB /* Kingfisher */ = {
isa = XCSwiftPackageProductDependency;
package = E43BEC9E238835DC00CAF1EB /* XCRemoteSwiftPackageReference "Kingfisher" */;
diff --git a/Persephone.xcodeproj/xcshareddata/xcschemes/Persephone-iOS.xcscheme b/Persephone.xcodeproj/xcshareddata/xcschemes/Persephone-iOS.xcscheme
new file mode 100644
index 0000000..68ab8fd
--- /dev/null
+++ b/Persephone.xcodeproj/xcshareddata/xcschemes/Persephone-iOS.xcscheme
@@ -0,0 +1,98 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Resources/export/iOS-1024x1024.png b/Resources/export/iOS-1024x1024.png
new file mode 100644
index 0000000..ab9d583
Binary files /dev/null and b/Resources/export/iOS-1024x1024.png differ
diff --git a/Resources/export/iOS-120x120.png b/Resources/export/iOS-120x120.png
new file mode 100644
index 0000000..3113588
Binary files /dev/null and b/Resources/export/iOS-120x120.png differ
diff --git a/Resources/export/iOS-152x152.png b/Resources/export/iOS-152x152.png
new file mode 100644
index 0000000..ef93bfb
Binary files /dev/null and b/Resources/export/iOS-152x152.png differ
diff --git a/Resources/export/iOS-167x167.png b/Resources/export/iOS-167x167.png
new file mode 100644
index 0000000..e8e6f5a
Binary files /dev/null and b/Resources/export/iOS-167x167.png differ
diff --git a/Resources/export/iOS-180x180.png b/Resources/export/iOS-180x180.png
new file mode 100644
index 0000000..4933413
Binary files /dev/null and b/Resources/export/iOS-180x180.png differ
diff --git a/Resources/export/iOS-20x20.png b/Resources/export/iOS-20x20.png
new file mode 100644
index 0000000..5826066
Binary files /dev/null and b/Resources/export/iOS-20x20.png differ
diff --git a/Resources/export/iOS-29x29.png b/Resources/export/iOS-29x29.png
new file mode 100644
index 0000000..b711b1d
Binary files /dev/null and b/Resources/export/iOS-29x29.png differ
diff --git a/Resources/export/iOS-40x40.png b/Resources/export/iOS-40x40.png
new file mode 100644
index 0000000..80df49d
Binary files /dev/null and b/Resources/export/iOS-40x40.png differ
diff --git a/Resources/export/iOS-58x58.png b/Resources/export/iOS-58x58.png
new file mode 100644
index 0000000..88ae461
Binary files /dev/null and b/Resources/export/iOS-58x58.png differ
diff --git a/Resources/export/iOS-60x60.png b/Resources/export/iOS-60x60.png
new file mode 100644
index 0000000..cbd7eac
Binary files /dev/null and b/Resources/export/iOS-60x60.png differ
diff --git a/Resources/export/iOS-76x76.png b/Resources/export/iOS-76x76.png
new file mode 100644
index 0000000..1281205
Binary files /dev/null and b/Resources/export/iOS-76x76.png differ
diff --git a/Resources/export/iOS-80x80.png b/Resources/export/iOS-80x80.png
new file mode 100644
index 0000000..72fa309
Binary files /dev/null and b/Resources/export/iOS-80x80.png differ
diff --git a/Resources/export/iOS-87x87.png b/Resources/export/iOS-87x87.png
new file mode 100644
index 0000000..55159f9
Binary files /dev/null and b/Resources/export/iOS-87x87.png differ
diff --git a/Resources/export/iosAppIcon.png b/Resources/export/iosAppIcon.png
new file mode 100644
index 0000000..2e41614
Binary files /dev/null and b/Resources/export/iosAppIcon.png differ
diff --git a/Resources/export/iosAppIcon@2x.png b/Resources/export/iosAppIcon@2x.png
new file mode 100644
index 0000000..7b6a09a
Binary files /dev/null and b/Resources/export/iosAppIcon@2x.png differ
diff --git a/Resources/export/nextTrackButtonHugeiOS.pdf b/Resources/export/nextTrackButtonHugeiOS.pdf
new file mode 100644
index 0000000..c1687a6
Binary files /dev/null and b/Resources/export/nextTrackButtonHugeiOS.pdf differ
diff --git a/Resources/export/nextTrackButtonHugeiOS.png b/Resources/export/nextTrackButtonHugeiOS.png
new file mode 100644
index 0000000..d3e38bf
Binary files /dev/null and b/Resources/export/nextTrackButtonHugeiOS.png differ
diff --git a/Resources/export/nextTrackButtonHugeiOS@2x.png b/Resources/export/nextTrackButtonHugeiOS@2x.png
new file mode 100644
index 0000000..3342900
Binary files /dev/null and b/Resources/export/nextTrackButtonHugeiOS@2x.png differ
diff --git a/Resources/export/nextTrackButtonHugeiOS@3x.png b/Resources/export/nextTrackButtonHugeiOS@3x.png
new file mode 100644
index 0000000..5131eda
Binary files /dev/null and b/Resources/export/nextTrackButtonHugeiOS@3x.png differ
diff --git a/Resources/export/nextTrackButtonLarge.pdf b/Resources/export/nextTrackButtonLarge.pdf
new file mode 100644
index 0000000..364255c
Binary files /dev/null and b/Resources/export/nextTrackButtonLarge.pdf differ
diff --git a/Resources/export/nextTrackButtonLarge.png b/Resources/export/nextTrackButtonLarge.png
new file mode 100644
index 0000000..3e05e82
Binary files /dev/null and b/Resources/export/nextTrackButtonLarge.png differ
diff --git a/Resources/export/nextTrackButtonLarge@2x.png b/Resources/export/nextTrackButtonLarge@2x.png
new file mode 100644
index 0000000..e3e7b10
Binary files /dev/null and b/Resources/export/nextTrackButtonLarge@2x.png differ
diff --git a/Resources/export/nextTrackButtonLarge@3x.png b/Resources/export/nextTrackButtonLarge@3x.png
new file mode 100644
index 0000000..89a7813
Binary files /dev/null and b/Resources/export/nextTrackButtonLarge@3x.png differ
diff --git a/Resources/export/nextTrackButtonLargeiOS.pdf b/Resources/export/nextTrackButtonLargeiOS.pdf
new file mode 100644
index 0000000..9f2f974
Binary files /dev/null and b/Resources/export/nextTrackButtonLargeiOS.pdf differ
diff --git a/Resources/export/nextTrackButtonLargeiOS.png b/Resources/export/nextTrackButtonLargeiOS.png
new file mode 100644
index 0000000..895f2d4
Binary files /dev/null and b/Resources/export/nextTrackButtonLargeiOS.png differ
diff --git a/Resources/export/nextTrackButtonLargeiOS@2x.png b/Resources/export/nextTrackButtonLargeiOS@2x.png
new file mode 100644
index 0000000..fa49346
Binary files /dev/null and b/Resources/export/nextTrackButtonLargeiOS@2x.png differ
diff --git a/Resources/export/nextTrackButtonLargeiOS@3x.png b/Resources/export/nextTrackButtonLargeiOS@3x.png
new file mode 100644
index 0000000..49dd944
Binary files /dev/null and b/Resources/export/nextTrackButtonLargeiOS@3x.png differ
diff --git a/Resources/export/pauseButtonHugeiOS.pdf b/Resources/export/pauseButtonHugeiOS.pdf
new file mode 100644
index 0000000..b47299f
Binary files /dev/null and b/Resources/export/pauseButtonHugeiOS.pdf differ
diff --git a/Resources/export/pauseButtonHugeiOS.png b/Resources/export/pauseButtonHugeiOS.png
new file mode 100644
index 0000000..29d5497
Binary files /dev/null and b/Resources/export/pauseButtonHugeiOS.png differ
diff --git a/Resources/export/pauseButtonHugeiOS@2x.png b/Resources/export/pauseButtonHugeiOS@2x.png
new file mode 100644
index 0000000..cf1ffcd
Binary files /dev/null and b/Resources/export/pauseButtonHugeiOS@2x.png differ
diff --git a/Resources/export/pauseButtonHugeiOS@3x.png b/Resources/export/pauseButtonHugeiOS@3x.png
new file mode 100644
index 0000000..6115b41
Binary files /dev/null and b/Resources/export/pauseButtonHugeiOS@3x.png differ
diff --git a/Resources/export/pauseButtonLargeiOS.pdf b/Resources/export/pauseButtonLargeiOS.pdf
new file mode 100644
index 0000000..4f49630
Binary files /dev/null and b/Resources/export/pauseButtonLargeiOS.pdf differ
diff --git a/Resources/export/pauseButtonLargeiOS.png b/Resources/export/pauseButtonLargeiOS.png
new file mode 100644
index 0000000..c56a775
Binary files /dev/null and b/Resources/export/pauseButtonLargeiOS.png differ
diff --git a/Resources/export/pauseButtonLargeiOS@2x.png b/Resources/export/pauseButtonLargeiOS@2x.png
new file mode 100644
index 0000000..792814e
Binary files /dev/null and b/Resources/export/pauseButtonLargeiOS@2x.png differ
diff --git a/Resources/export/pauseButtonLargeiOS@3x.png b/Resources/export/pauseButtonLargeiOS@3x.png
new file mode 100644
index 0000000..605efca
Binary files /dev/null and b/Resources/export/pauseButtonLargeiOS@3x.png differ
diff --git a/Resources/export/playButtonHugeiOS.pdf b/Resources/export/playButtonHugeiOS.pdf
new file mode 100644
index 0000000..81115af
Binary files /dev/null and b/Resources/export/playButtonHugeiOS.pdf differ
diff --git a/Resources/export/playButtonHugeiOS.png b/Resources/export/playButtonHugeiOS.png
new file mode 100644
index 0000000..a8e7f2c
Binary files /dev/null and b/Resources/export/playButtonHugeiOS.png differ
diff --git a/Resources/export/playButtonHugeiOS@2x.png b/Resources/export/playButtonHugeiOS@2x.png
new file mode 100644
index 0000000..9638f00
Binary files /dev/null and b/Resources/export/playButtonHugeiOS@2x.png differ
diff --git a/Resources/export/playButtonHugeiOS@3x.png b/Resources/export/playButtonHugeiOS@3x.png
new file mode 100644
index 0000000..a620610
Binary files /dev/null and b/Resources/export/playButtonHugeiOS@3x.png differ
diff --git a/Resources/export/playButtonLarge.pdf b/Resources/export/playButtonLarge.pdf
index a04b172..b354ccb 100644
Binary files a/Resources/export/playButtonLarge.pdf and b/Resources/export/playButtonLarge.pdf differ
diff --git a/Resources/export/playButtonLarge.png b/Resources/export/playButtonLarge.png
index a489b7b..9e29316 100644
Binary files a/Resources/export/playButtonLarge.png and b/Resources/export/playButtonLarge.png differ
diff --git a/Resources/export/playButtonLarge@2x.png b/Resources/export/playButtonLarge@2x.png
index 109a9ca..4604852 100644
Binary files a/Resources/export/playButtonLarge@2x.png and b/Resources/export/playButtonLarge@2x.png differ
diff --git a/Resources/export/playButtonLarge@3x.png b/Resources/export/playButtonLarge@3x.png
new file mode 100644
index 0000000..32fe1e6
Binary files /dev/null and b/Resources/export/playButtonLarge@3x.png differ
diff --git a/Resources/export/playButtonLargeiOS.pdf b/Resources/export/playButtonLargeiOS.pdf
new file mode 100644
index 0000000..38ed773
Binary files /dev/null and b/Resources/export/playButtonLargeiOS.pdf differ
diff --git a/Resources/export/playButtonLargeiOS.png b/Resources/export/playButtonLargeiOS.png
new file mode 100644
index 0000000..8eaa316
Binary files /dev/null and b/Resources/export/playButtonLargeiOS.png differ
diff --git a/Resources/export/playButtonLargeiOS@2x.png b/Resources/export/playButtonLargeiOS@2x.png
new file mode 100644
index 0000000..a00fe2d
Binary files /dev/null and b/Resources/export/playButtonLargeiOS@2x.png differ
diff --git a/Resources/export/playButtonLargeiOS@3x.png b/Resources/export/playButtonLargeiOS@3x.png
new file mode 100644
index 0000000..41ab03f
Binary files /dev/null and b/Resources/export/playButtonLargeiOS@3x.png differ
diff --git a/Resources/export/prevTrackButtonHugeiOS.pdf b/Resources/export/prevTrackButtonHugeiOS.pdf
new file mode 100644
index 0000000..d4eb770
Binary files /dev/null and b/Resources/export/prevTrackButtonHugeiOS.pdf differ
diff --git a/Resources/export/prevTrackButtonHugeiOS.png b/Resources/export/prevTrackButtonHugeiOS.png
new file mode 100644
index 0000000..1f813bc
Binary files /dev/null and b/Resources/export/prevTrackButtonHugeiOS.png differ
diff --git a/Resources/export/prevTrackButtonHugeiOS@2x.png b/Resources/export/prevTrackButtonHugeiOS@2x.png
new file mode 100644
index 0000000..1959763
Binary files /dev/null and b/Resources/export/prevTrackButtonHugeiOS@2x.png differ
diff --git a/Resources/icons.sketch b/Resources/icons.sketch
index 3ac177b..fb6fb32 100644
Binary files a/Resources/icons.sketch and b/Resources/icons.sketch differ
diff --git a/Shared/Controllers/MPDServerController.swift b/Shared/Controllers/MPDServerController.swift
index 909890d..ac1eeea 100644
--- a/Shared/Controllers/MPDServerController.swift
+++ b/Shared/Controllers/MPDServerController.swift
@@ -21,8 +21,10 @@ class MPDServerController {
func connect() {
let mpdServer = App.store.state.preferencesState.mpdServer
+ guard let host = mpdServer.hostOrDefault else { return }
+
App.mpdClient.connect(
- host: mpdServer.hostOrDefault,
+ host: host,
port: mpdServer.portOrDefault
)
}
diff --git a/Mac/Components/Shared/ImageDataProviders/MPDAlbumArtImageDataProvider.swift b/Shared/ImageDataProviders/MPDAlbumArtImageDataProvider.swift
similarity index 100%
rename from Mac/Components/Shared/ImageDataProviders/MPDAlbumArtImageDataProvider.swift
rename to Shared/ImageDataProviders/MPDAlbumArtImageDataProvider.swift
diff --git a/Shared/MPDClient/Extensions/MPDClient+Album.swift b/Shared/MPDClient/Extensions/MPDClient+Album.swift
index 7c3a1bc..bf1e68c 100644
--- a/Shared/MPDClient/Extensions/MPDClient+Album.swift
+++ b/Shared/MPDClient/Extensions/MPDClient+Album.swift
@@ -57,7 +57,7 @@ extension MPDClient {
func albums(filter: String) {
var albums: [MPDAlbum] = []
- mpd_search_db_songs(self.connection, false)
+ mpd_search_db_songs(connection, false)
if filter != "" {
let escapedFilter = filter.replacingOccurrences(of: "'", with: "\\'")
@@ -66,11 +66,12 @@ extension MPDClient {
"(any contains '\(escapedFilter)')"
)
}
- mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_TRACK, "1")
+ mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT, MPD_TAG_TRACK, "1")
+ mpd_search_add_sort_tag(connection, MPD_TAG_ALBUM_ARTIST_SORT, false)
mpd_search_commit(self.connection)
- while let song = mpd_recv_song(self.connection) {
+ while let song = mpd_recv_song(connection) {
let mpdSong = MPDSong(song)
let mpdAlbum = MPDAlbum(
@@ -92,10 +93,10 @@ extension MPDClient {
var firstSong: MPDSong?
- mpd_search_db_songs(self.connection, true)
- mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM, album.title)
- mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM_ARTIST, album.artist)
- mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_TRACK, "1")
+ mpd_search_db_songs(connection, true)
+ mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM, album.title)
+ mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ALBUM_ARTIST, album.artist)
+ mpd_search_add_tag_constraint(connection, MPD_OPERATOR_DEFAULT, MPD_TAG_TRACK, "1")
mpd_search_commit(self.connection)
diff --git a/Shared/MPDClient/Extensions/MPDClient+Connection.swift b/Shared/MPDClient/Extensions/MPDClient+Connection.swift
index 589cc2b..1a87b99 100644
--- a/Shared/MPDClient/Extensions/MPDClient+Connection.swift
+++ b/Shared/MPDClient/Extensions/MPDClient+Connection.swift
@@ -35,19 +35,21 @@ extension MPDClient {
self.delegate?.willDisconnect(mpdClient: self)
- mpd_connection_free(self.connection)
- self.isConnected = false
+ mpd_connection_free(self.connection)
+ self.isConnected = false
}
func connect(host: String, port: Int) {
- let commandOperation = BlockOperation() { [unowned self] in
- self.sendCommand(command: .connect, userData: ["host": host, "port": port])
+ if !isConnected {
+ let commandOperation = BlockOperation() { [unowned self] in
+ self.sendCommand(command: .connect, userData: ["host": host, "port": port])
- if self.isConnected {
- self.idle()
+ if self.isConnected {
+ self.idle()
+ }
}
+ commandQueue.addOperation(commandOperation)
}
- commandQueue.addOperation(commandOperation)
}
func disconnect() {
diff --git a/Shared/Models/AlbumSongItem.swift b/Shared/Models/AlbumSongItem.swift
new file mode 100644
index 0000000..e9b5fd2
--- /dev/null
+++ b/Shared/Models/AlbumSongItem.swift
@@ -0,0 +1,24 @@
+//
+// AlbumSongItem.swift
+// Persephone-iOS
+//
+// Created by Daniel Barber on 2020-3-30.
+// Copyright © 2020 Dan Barber. All rights reserved.
+//
+
+import Foundation
+
+struct AlbumSongItem {
+ let disc: String?
+ let song: Song?
+
+ init(song: Song) {
+ self.disc = nil
+ self.song = song
+ }
+
+ init(disc: String) {
+ self.disc = disc
+ self.song = nil
+ }
+}
diff --git a/Shared/Models/MPDServer.swift b/Shared/Models/MPDServer.swift
index 63f00ea..5af31db 100644
--- a/Shared/Models/MPDServer.swift
+++ b/Shared/Models/MPDServer.swift
@@ -7,12 +7,17 @@
//
struct MPDServer {
- let hostDefault = "127.0.0.1"
+ #if os(macOS)
+ let hostDefault: String? = "127.0.0.1"
+ #else
+ let hostDefault: String? = nil
+ #endif
+
let portDefault = 6600
var host: String?
- var hostOrDefault: String {
+ var hostOrDefault: String? {
return host ?? hostDefault
}
diff --git a/Shared/Models/Time.swift b/Shared/Models/Time.swift
index 4c2596f..5629691 100644
--- a/Shared/Models/Time.swift
+++ b/Shared/Models/Time.swift
@@ -25,4 +25,8 @@ struct Time {
return formatter.string(from: TimeInterval(timeInSeconds))!
}
+
+ var hours: Int { timeInSeconds / 3600 }
+
+ var minutes: Int { timeInSeconds % 3600 / 60 }
}
diff --git a/iOS/App.swift b/iOS/App.swift
index 89861e4..fcc70ac 100644
--- a/iOS/App.swift
+++ b/iOS/App.swift
@@ -11,7 +11,6 @@ import ReSwift
struct App {
static let store = Store(reducer: appReducer, state: nil)
static let trackTimer = TrackTimer()
- //static let userNotificationsController = UserNotificationsController()
static let mpdServerDelegate = MPDServerDelegate()
static let mpdServerController = MPDServerController(delegate: mpdServerDelegate)
static var mpdClient: MPDClient!
diff --git a/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..065e2ac
--- /dev/null
+++ b/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,116 @@
+{
+ "images" : [
+ {
+ "filename" : "iOS-40x40-1.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "iOS-60x60.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "iOS-58x58-1.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "iOS-87x87.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "iOS-80x80.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "iOS-120x120.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "iOS-120x120-1.png",
+ "idiom" : "iphone",
+ "scale" : "2x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "iOS-180x180.png",
+ "idiom" : "iphone",
+ "scale" : "3x",
+ "size" : "60x60"
+ },
+ {
+ "filename" : "iOS-20x20.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "iOS-40x40.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "20x20"
+ },
+ {
+ "filename" : "iOS-29x29.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "iOS-58x58.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "29x29"
+ },
+ {
+ "filename" : "iOS-40x40-2.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "iOS-80x80-1.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "40x40"
+ },
+ {
+ "filename" : "iOS-76x76.png",
+ "idiom" : "ipad",
+ "scale" : "1x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "iOS-152x152.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "76x76"
+ },
+ {
+ "filename" : "iOS-167x167.png",
+ "idiom" : "ipad",
+ "scale" : "2x",
+ "size" : "83.5x83.5"
+ },
+ {
+ "filename" : "iOS-1024x1024.png",
+ "idiom" : "ios-marketing",
+ "scale" : "1x",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/iOS/Assets.xcassets/AppIcon.appiconset/iOS-1024x1024.png b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-1024x1024.png
new file mode 100644
index 0000000..ab9d583
Binary files /dev/null and b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-1024x1024.png differ
diff --git a/iOS/Assets.xcassets/AppIcon.appiconset/iOS-120x120-1.png b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-120x120-1.png
new file mode 100644
index 0000000..3113588
Binary files /dev/null and b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-120x120-1.png differ
diff --git a/iOS/Assets.xcassets/AppIcon.appiconset/iOS-120x120.png b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-120x120.png
new file mode 100644
index 0000000..3113588
Binary files /dev/null and b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-120x120.png differ
diff --git a/iOS/Assets.xcassets/AppIcon.appiconset/iOS-152x152.png b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-152x152.png
new file mode 100644
index 0000000..ef93bfb
Binary files /dev/null and b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-152x152.png differ
diff --git a/iOS/Assets.xcassets/AppIcon.appiconset/iOS-167x167.png b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-167x167.png
new file mode 100644
index 0000000..e8e6f5a
Binary files /dev/null and b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-167x167.png differ
diff --git a/iOS/Assets.xcassets/AppIcon.appiconset/iOS-180x180.png b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-180x180.png
new file mode 100644
index 0000000..4933413
Binary files /dev/null and b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-180x180.png differ
diff --git a/iOS/Assets.xcassets/AppIcon.appiconset/iOS-20x20.png b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-20x20.png
new file mode 100644
index 0000000..5826066
Binary files /dev/null and b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-20x20.png differ
diff --git a/iOS/Assets.xcassets/AppIcon.appiconset/iOS-29x29.png b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-29x29.png
new file mode 100644
index 0000000..b711b1d
Binary files /dev/null and b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-29x29.png differ
diff --git a/iOS/Assets.xcassets/AppIcon.appiconset/iOS-40x40-1.png b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-40x40-1.png
new file mode 100644
index 0000000..80df49d
Binary files /dev/null and b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-40x40-1.png differ
diff --git a/iOS/Assets.xcassets/AppIcon.appiconset/iOS-40x40-2.png b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-40x40-2.png
new file mode 100644
index 0000000..80df49d
Binary files /dev/null and b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-40x40-2.png differ
diff --git a/iOS/Assets.xcassets/AppIcon.appiconset/iOS-40x40.png b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-40x40.png
new file mode 100644
index 0000000..80df49d
Binary files /dev/null and b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-40x40.png differ
diff --git a/iOS/Assets.xcassets/AppIcon.appiconset/iOS-58x58-1.png b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-58x58-1.png
new file mode 100644
index 0000000..88ae461
Binary files /dev/null and b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-58x58-1.png differ
diff --git a/iOS/Assets.xcassets/AppIcon.appiconset/iOS-58x58.png b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-58x58.png
new file mode 100644
index 0000000..88ae461
Binary files /dev/null and b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-58x58.png differ
diff --git a/iOS/Assets.xcassets/AppIcon.appiconset/iOS-60x60.png b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-60x60.png
new file mode 100644
index 0000000..cbd7eac
Binary files /dev/null and b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-60x60.png differ
diff --git a/iOS/Assets.xcassets/AppIcon.appiconset/iOS-76x76.png b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-76x76.png
new file mode 100644
index 0000000..1281205
Binary files /dev/null and b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-76x76.png differ
diff --git a/iOS/Assets.xcassets/AppIcon.appiconset/iOS-80x80-1.png b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-80x80-1.png
new file mode 100644
index 0000000..72fa309
Binary files /dev/null and b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-80x80-1.png differ
diff --git a/iOS/Assets.xcassets/AppIcon.appiconset/iOS-80x80.png b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-80x80.png
new file mode 100644
index 0000000..72fa309
Binary files /dev/null and b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-80x80.png differ
diff --git a/iOS/Assets.xcassets/AppIcon.appiconset/iOS-87x87.png b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-87x87.png
new file mode 100644
index 0000000..95371dc
Binary files /dev/null and b/iOS/Assets.xcassets/AppIcon.appiconset/iOS-87x87.png differ
diff --git a/iOS/Assets.xcassets/Contents.json b/iOS/Assets.xcassets/Contents.json
new file mode 100644
index 0000000..73c0059
--- /dev/null
+++ b/iOS/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/iOS/Assets.xcassets/defaultCoverArt.imageset/Contents.json b/iOS/Assets.xcassets/defaultCoverArt.imageset/Contents.json
new file mode 100644
index 0000000..3312479
--- /dev/null
+++ b/iOS/Assets.xcassets/defaultCoverArt.imageset/Contents.json
@@ -0,0 +1,26 @@
+{
+ "images" : [
+ {
+ "filename" : "blankAlbumLight.pdf",
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "filename" : "blankAlbumDark.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "auto-scaling" : "auto",
+ "preserves-vector-representation" : true
+ }
+}
diff --git a/iOS/Assets.xcassets/defaultCoverArt.imageset/blankAlbumDark.pdf b/iOS/Assets.xcassets/defaultCoverArt.imageset/blankAlbumDark.pdf
new file mode 100644
index 0000000..01942a6
Binary files /dev/null and b/iOS/Assets.xcassets/defaultCoverArt.imageset/blankAlbumDark.pdf differ
diff --git a/iOS/Assets.xcassets/defaultCoverArt.imageset/blankAlbumLight.pdf b/iOS/Assets.xcassets/defaultCoverArt.imageset/blankAlbumLight.pdf
new file mode 100644
index 0000000..ed083f0
Binary files /dev/null and b/iOS/Assets.xcassets/defaultCoverArt.imageset/blankAlbumLight.pdf differ
diff --git a/iOS/Assets.xcassets/nextTrackButtonHugeiOS.imageset/Contents.json b/iOS/Assets.xcassets/nextTrackButtonHugeiOS.imageset/Contents.json
new file mode 100644
index 0000000..1e7587e
--- /dev/null
+++ b/iOS/Assets.xcassets/nextTrackButtonHugeiOS.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "filename" : "nextTrackButtonHugeiOS.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true,
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/iOS/Assets.xcassets/nextTrackButtonHugeiOS.imageset/nextTrackButtonHugeiOS.pdf b/iOS/Assets.xcassets/nextTrackButtonHugeiOS.imageset/nextTrackButtonHugeiOS.pdf
new file mode 100644
index 0000000..c1687a6
Binary files /dev/null and b/iOS/Assets.xcassets/nextTrackButtonHugeiOS.imageset/nextTrackButtonHugeiOS.pdf differ
diff --git a/iOS/Assets.xcassets/nextTrackButtonLarge.imageset/Contents.json b/iOS/Assets.xcassets/nextTrackButtonLarge.imageset/Contents.json
new file mode 100644
index 0000000..6f87921
--- /dev/null
+++ b/iOS/Assets.xcassets/nextTrackButtonLarge.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "nextTrackButtonLargeiOS.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/iOS/Assets.xcassets/nextTrackButtonLarge.imageset/nextTrackButtonLargeiOS.pdf b/iOS/Assets.xcassets/nextTrackButtonLarge.imageset/nextTrackButtonLargeiOS.pdf
new file mode 100644
index 0000000..d560bd4
Binary files /dev/null and b/iOS/Assets.xcassets/nextTrackButtonLarge.imageset/nextTrackButtonLargeiOS.pdf differ
diff --git a/iOS/Assets.xcassets/pauseButtonHugeiOS.imageset/Contents.json b/iOS/Assets.xcassets/pauseButtonHugeiOS.imageset/Contents.json
new file mode 100644
index 0000000..62a34b5
--- /dev/null
+++ b/iOS/Assets.xcassets/pauseButtonHugeiOS.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "filename" : "pauseButtonHugeiOS.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true,
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/iOS/Assets.xcassets/pauseButtonHugeiOS.imageset/pauseButtonHugeiOS.pdf b/iOS/Assets.xcassets/pauseButtonHugeiOS.imageset/pauseButtonHugeiOS.pdf
new file mode 100644
index 0000000..b47299f
Binary files /dev/null and b/iOS/Assets.xcassets/pauseButtonHugeiOS.imageset/pauseButtonHugeiOS.pdf differ
diff --git a/iOS/Assets.xcassets/pauseButtonLarge.imageset/Contents.json b/iOS/Assets.xcassets/pauseButtonLarge.imageset/Contents.json
new file mode 100644
index 0000000..56a5a2c
--- /dev/null
+++ b/iOS/Assets.xcassets/pauseButtonLarge.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "pauseButtonLargeiOS.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/iOS/Assets.xcassets/pauseButtonLarge.imageset/pauseButtonLargeiOS.pdf b/iOS/Assets.xcassets/pauseButtonLarge.imageset/pauseButtonLargeiOS.pdf
new file mode 100644
index 0000000..0680892
Binary files /dev/null and b/iOS/Assets.xcassets/pauseButtonLarge.imageset/pauseButtonLargeiOS.pdf differ
diff --git a/iOS/Assets.xcassets/playButtonHugeiOS.imageset/Contents.json b/iOS/Assets.xcassets/playButtonHugeiOS.imageset/Contents.json
new file mode 100644
index 0000000..c9a0c7c
--- /dev/null
+++ b/iOS/Assets.xcassets/playButtonHugeiOS.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "filename" : "playButtonHugeiOS.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true,
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/iOS/Assets.xcassets/playButtonHugeiOS.imageset/playButtonHugeiOS.pdf b/iOS/Assets.xcassets/playButtonHugeiOS.imageset/playButtonHugeiOS.pdf
new file mode 100644
index 0000000..81115af
Binary files /dev/null and b/iOS/Assets.xcassets/playButtonHugeiOS.imageset/playButtonHugeiOS.pdf differ
diff --git a/iOS/Assets.xcassets/playButtonLarge.imageset/Contents.json b/iOS/Assets.xcassets/playButtonLarge.imageset/Contents.json
new file mode 100644
index 0000000..2276e67
--- /dev/null
+++ b/iOS/Assets.xcassets/playButtonLarge.imageset/Contents.json
@@ -0,0 +1,15 @@
+{
+ "images" : [
+ {
+ "filename" : "playButtonLargeiOS.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/iOS/Assets.xcassets/playButtonLarge.imageset/playButtonLargeiOS.pdf b/iOS/Assets.xcassets/playButtonLarge.imageset/playButtonLargeiOS.pdf
new file mode 100644
index 0000000..84497df
Binary files /dev/null and b/iOS/Assets.xcassets/playButtonLarge.imageset/playButtonLargeiOS.pdf differ
diff --git a/iOS/Assets.xcassets/prevTrackButtonHugeiOS.imageset/Contents.json b/iOS/Assets.xcassets/prevTrackButtonHugeiOS.imageset/Contents.json
new file mode 100644
index 0000000..57d239b
--- /dev/null
+++ b/iOS/Assets.xcassets/prevTrackButtonHugeiOS.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "filename" : "prevTrackButtonHugeiOS.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true,
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/iOS/Assets.xcassets/prevTrackButtonHugeiOS.imageset/prevTrackButtonHugeiOS.pdf b/iOS/Assets.xcassets/prevTrackButtonHugeiOS.imageset/prevTrackButtonHugeiOS.pdf
new file mode 100644
index 0000000..d4eb770
Binary files /dev/null and b/iOS/Assets.xcassets/prevTrackButtonHugeiOS.imageset/prevTrackButtonHugeiOS.pdf differ
diff --git a/iOS/Assets.xcassets/speakerHigh.imageset/Contents.json b/iOS/Assets.xcassets/speakerHigh.imageset/Contents.json
new file mode 100644
index 0000000..8a644e7
--- /dev/null
+++ b/iOS/Assets.xcassets/speakerHigh.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "filename" : "speakerHigh.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true,
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/iOS/Assets.xcassets/speakerHigh.imageset/speakerHigh.pdf b/iOS/Assets.xcassets/speakerHigh.imageset/speakerHigh.pdf
new file mode 100644
index 0000000..08ac5f1
Binary files /dev/null and b/iOS/Assets.xcassets/speakerHigh.imageset/speakerHigh.pdf differ
diff --git a/iOS/Assets.xcassets/speakerOff.imageset/Contents.json b/iOS/Assets.xcassets/speakerOff.imageset/Contents.json
new file mode 100644
index 0000000..5e29376
--- /dev/null
+++ b/iOS/Assets.xcassets/speakerOff.imageset/Contents.json
@@ -0,0 +1,16 @@
+{
+ "images" : [
+ {
+ "filename" : "speakerOff.pdf",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ },
+ "properties" : {
+ "preserves-vector-representation" : true,
+ "template-rendering-intent" : "template"
+ }
+}
diff --git a/iOS/Assets.xcassets/speakerOff.imageset/speakerOff.pdf b/iOS/Assets.xcassets/speakerOff.imageset/speakerOff.pdf
new file mode 100644
index 0000000..a258eea
Binary files /dev/null and b/iOS/Assets.xcassets/speakerOff.imageset/speakerOff.pdf differ
diff --git a/iOS/Base.lproj/LaunchScreen.storyboard b/iOS/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..865e932
--- /dev/null
+++ b/iOS/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/iOS/Base.lproj/Main.storyboard b/iOS/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..3a12470
--- /dev/null
+++ b/iOS/Base.lproj/Main.storyboard
@@ -0,0 +1,336 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/iOS/Components/Browser/Album Browser/AlbumDataSource.swift b/iOS/Components/Browser/Album Browser/AlbumDataSource.swift
new file mode 100644
index 0000000..304ed4a
--- /dev/null
+++ b/iOS/Components/Browser/Album Browser/AlbumDataSource.swift
@@ -0,0 +1,26 @@
+//
+// AlbumDataSource.swift
+// Persephone
+//
+// Created by Daniel Barber on 2019/2/20.
+// Copyright © 2019 Dan Barber. All rights reserved.
+//
+
+import UIKit
+
+class AlbumDataSource: NSObject, UICollectionViewDataSource {
+ var albums: [Album] = []
+
+ func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
+ return albums.count
+ }
+
+ func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+ let item = collectionView.dequeueReusableCell(withReuseIdentifier: "AlbumViewCell", for: indexPath)
+ guard let albumViewCell = item as? AlbumItemCell else { return item }
+
+ albumViewCell.setAlbum(albums[indexPath.item])
+
+ return albumViewCell
+ }
+}
diff --git a/iOS/Components/Browser/Album Browser/AlbumItemCell.swift b/iOS/Components/Browser/Album Browser/AlbumItemCell.swift
new file mode 100644
index 0000000..44a4f1b
--- /dev/null
+++ b/iOS/Components/Browser/Album Browser/AlbumItemCell.swift
@@ -0,0 +1,64 @@
+//
+// AlbumItemCell.swift
+// Persephone
+//
+// Created by Daniel Barber on 2020-3-28.
+// Copyright © 2020 Dan Barber. All rights reserved.
+//
+
+import UIKit
+import Kingfisher
+
+class AlbumItemCell: UICollectionViewCell {
+ var album: Album?
+
+ override func didMoveToSuperview() {
+ albumCoverView.layer.backgroundColor = UIColor.black.cgColor
+ albumCoverView.layer.cornerRadius = 4
+ albumCoverView.layer.borderWidth = 1 / traitCollection.displayScale
+ albumCoverView.layer.masksToBounds = true
+ setAppearance()
+ }
+
+ func setAlbum(_ album: Album) {
+ self.album = album
+ albumTitle.text = album.title
+ albumArtist.text = album.artist
+ setAlbumCover(album)
+ }
+
+ func setAlbumCover(_ album: Album) {
+ guard let song = album.mpdAlbum.firstSong
+ else { return }
+
+ let provider = MPDAlbumArtImageDataProvider(
+ songUri: song.uriString,
+ cacheKey: album.hash
+ )
+
+ albumCoverView.kf.setImage(
+ with: .provider(provider),
+ placeholder: UIImage(named: "defaultCoverArt"),
+ options: [
+ .processor(DownsamplingImageProcessor(size: .albumListCoverSize)),
+ .scaleFactor(traitCollection.displayScale),
+ ]
+ )
+ }
+
+ override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
+ if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
+ setAppearance()
+ }
+ }
+
+ func setAppearance() {
+ let darkMode = traitCollection.userInterfaceStyle == .dark
+
+ albumCoverView.layer.borderColor = darkMode ? CGColor.albumBorderColorDark : CGColor.albumBorderColorLight
+ }
+
+ @IBOutlet var albumCoverView: UIImageView!
+ @IBOutlet var albumTitle: UILabel!
+ @IBOutlet var albumArtist: UILabel!
+}
diff --git a/iOS/Components/Browser/Album Browser/AlbumViewController+UICollectionViewDelegateFlowLayout.swift b/iOS/Components/Browser/Album Browser/AlbumViewController+UICollectionViewDelegateFlowLayout.swift
new file mode 100644
index 0000000..70643c7
--- /dev/null
+++ b/iOS/Components/Browser/Album Browser/AlbumViewController+UICollectionViewDelegateFlowLayout.swift
@@ -0,0 +1,33 @@
+//
+// AlbumViewController+UICollectionViewDelegateFlowLayout.swift
+// Persephone
+//
+// Created by Daniel Barber on 2020-3-28.
+// Copyright © 2020 Dan Barber. All rights reserved.
+//
+
+import UIKit
+
+extension AlbumViewController: UICollectionViewDelegateFlowLayout {
+ func collectionView(_ collectionView: UICollectionView,
+ layout collectionViewLayout: UICollectionViewLayout,
+ sizeForItemAt indexPath: IndexPath) -> CGSize {
+ let paddingSpace = sectionInsets.left * (itemsPerRow + 1)
+ let availableWidth = view.frame.width - paddingSpace
+ let widthPerItem = availableWidth / itemsPerRow
+
+ return CGSize(width: widthPerItem, height: widthPerItem + 48)
+ }
+
+ func collectionView(_ collectionView: UICollectionView,
+ layout collectionViewLayout: UICollectionViewLayout,
+ insetForSectionAt section: Int) -> UIEdgeInsets {
+ return sectionInsets
+ }
+
+ func collectionView(_ collectionView: UICollectionView,
+ layout collectionViewLayout: UICollectionViewLayout,
+ minimumLineSpacingForSectionAt section: Int) -> CGFloat {
+ return sectionInsets.left / 2
+ }
+}
diff --git a/iOS/Components/Browser/Album Browser/AlbumViewController.swift b/iOS/Components/Browser/Album Browser/AlbumViewController.swift
new file mode 100644
index 0000000..5480a89
--- /dev/null
+++ b/iOS/Components/Browser/Album Browser/AlbumViewController.swift
@@ -0,0 +1,60 @@
+//
+// ViewController.swift
+// Persephone-iOS
+//
+// Created by Daniel Barber on 2020-3-13.
+// Copyright © 2020 Dan Barber. All rights reserved.
+//
+
+import UIKit
+import ReSwift
+
+class AlbumViewController: UICollectionViewController {
+ let dataSource = AlbumDataSource()
+
+ let itemsPerRow: CGFloat = 2
+
+ let sectionInsets = UIEdgeInsets(
+ top: 20.0,
+ left: 20.0,
+ bottom: 30.0,
+ right: 20.0
+ )
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ albumCollectionView.dataSource = dataSource
+
+ App.store.subscribe(self) {
+ $0.select { $0.albumListState }
+ }
+
+ NotificationCenter.default.addObserver(self, selector: #selector(didConnect), name: .didConnect, object: nil)
+ }
+
+ @objc func didConnect() {
+ print("Album view controller - Connected!")
+ App.mpdClient.fetchAllAlbums()
+ }
+
+ override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+ guard let detailView = segue.destination as? AlbumSongListViewController,
+ let sender = sender as? AlbumItemCell
+ else { return }
+
+ detailView.setAlbum(sender.album)
+ }
+
+ @IBOutlet var albumCollectionView: UICollectionView!
+}
+
+extension AlbumViewController: StoreSubscriber {
+ typealias StoreSubscriberStateType = AlbumListState
+
+ func newState(state: StoreSubscriberStateType) {
+ dataSource.albums = state.albums
+
+ albumCollectionView.reloadData()
+ }
+}
diff --git a/iOS/Components/Browser/Album Detail/AlbumDetailFooterView.swift b/iOS/Components/Browser/Album Detail/AlbumDetailFooterView.swift
new file mode 100644
index 0000000..451f2e8
--- /dev/null
+++ b/iOS/Components/Browser/Album Detail/AlbumDetailFooterView.swift
@@ -0,0 +1,25 @@
+//
+// AlbumDetailFooterView.swift
+// Persephone
+//
+// Created by Daniel Barber on 2020-4-2.
+// Copyright © 2020 Dan Barber. All rights reserved.
+//
+
+import UIKit
+
+class AlbumDetailFooterView: UIView {
+ override func didMoveToSuperview() {
+ super.didMoveToSuperview()
+
+ let separator = CALayer()
+ separator.frame = CGRect(
+ x: 20,
+ y: -0.5,
+ width: UIScreen.main.bounds.width - 20,
+ height: 0.5
+ )
+ separator.backgroundColor = UIColor.separator.cgColor
+ layer.addSublayer(separator)
+ }
+}
diff --git a/iOS/Components/Browser/Album Detail/AlbumDiscCell.swift b/iOS/Components/Browser/Album Detail/AlbumDiscCell.swift
new file mode 100644
index 0000000..4accafa
--- /dev/null
+++ b/iOS/Components/Browser/Album Detail/AlbumDiscCell.swift
@@ -0,0 +1,21 @@
+//
+// AlbumDiscCell.swift
+// Persephone-iOS
+//
+// Created by Daniel Barber on 2020-3-31.
+// Copyright © 2020 Dan Barber. All rights reserved.
+//
+
+import UIKit
+
+class AlbumDiscCell: UITableViewCell {
+ var albumSongItem: AlbumSongItem?
+
+ func setSongItem(songItem: AlbumSongItem) {
+ albumSongItem = songItem
+
+ albumDisc.text = "Disc \(songItem.disc ?? "0")"
+ }
+
+ @IBOutlet var albumDisc: UILabel!
+}
diff --git a/iOS/Components/Browser/Album Detail/AlbumSongCell.swift b/iOS/Components/Browser/Album Detail/AlbumSongCell.swift
new file mode 100644
index 0000000..11edc51
--- /dev/null
+++ b/iOS/Components/Browser/Album Detail/AlbumSongCell.swift
@@ -0,0 +1,29 @@
+//
+// AlbumSongCell.swift
+// Persephone-iOS
+//
+// Created by Daniel Barber on 2020-3-30.
+// Copyright © 2020 Dan Barber. All rights reserved.
+//
+
+import UIKit
+
+class AlbumSongCell: UITableViewCell {
+ var albumSongItem: AlbumSongItem?
+
+ func setSongItem(songItem: AlbumSongItem) {
+ albumSongItem = songItem
+
+ guard let song = songItem.song else { return }
+
+ songDuration.font = .timerFont
+
+ trackNumber.text = song.trackNumber
+ songTitle.text = song.title
+ songDuration.text = song.duration.formattedTime
+ }
+
+ @IBOutlet var trackNumber: UILabel!
+ @IBOutlet var songTitle: UILabel!
+ @IBOutlet var songDuration: UILabel!
+}
diff --git a/iOS/Components/Browser/Album Detail/AlbumSongListViewController.swift b/iOS/Components/Browser/Album Detail/AlbumSongListViewController.swift
new file mode 100644
index 0000000..26c9f3d
--- /dev/null
+++ b/iOS/Components/Browser/Album Detail/AlbumSongListViewController.swift
@@ -0,0 +1,155 @@
+//
+// AlbumSongListViewController.swift
+// Persephone
+//
+// Created by Daniel Barber on 2020-3-30.
+// Copyright © 2020 Dan Barber. All rights reserved.
+//
+
+import UIKit
+import Kingfisher
+
+class AlbumSongListViewController: UITableViewController {
+ var album: Album?
+ var albumSongs: [Song] = []
+ var dataSource = AlbumTracksDataSource()
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ albumTracksView.dataSource = dataSource
+ albumTracksView.layoutMargins = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20)
+
+ navigationItem.largeTitleDisplayMode = .never
+
+ albumCoverView.layer.backgroundColor = UIColor.black.cgColor
+ albumCoverView.layer.cornerRadius = 4
+ albumCoverView.layer.borderWidth = 1 / traitCollection.displayScale
+ albumCoverView.layer.masksToBounds = true
+
+ playAlbumButton.layer.cornerRadius = 8
+ addAlbumToQueueButton.layer.cornerRadius = 8
+ setAppearance()
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ super.viewWillAppear(animated)
+
+ guard let album = album else { return }
+
+ getAlbumSongs(for: album)
+
+ albumTitle.text = album.title
+ albumMetadata.text = "\(album.artist) · \(album.date)"
+ }
+
+ override func viewWillLayoutSubviews() {
+ var layoutSize = UIView.layoutFittingExpandedSize
+ layoutSize.width = view.bounds.width
+
+ let headerViewSize = albumTracksView.tableHeaderView?
+ .systemLayoutSizeFitting(
+ layoutSize,
+ withHorizontalFittingPriority: .defaultHigh,
+ verticalFittingPriority: .defaultLow
+ )
+
+ albumTracksView.tableHeaderView?.frame.size = headerViewSize!
+ }
+
+ func setAlbum(_ album: Album?) {
+ guard let album = album else { return }
+
+ self.album = album
+ }
+
+ func getAlbumSongs(for album: Album) {
+ App.mpdClient.getAlbumSongs(for: album.mpdAlbum) { [weak self] (mpdSongs: [MPDClient.MPDSong]) in
+ guard let self = self else { return }
+
+ DispatchQueue.main.async {
+ var totalDurationInSeconds = 0
+
+ self.dataSource.setAlbumSongs(
+ mpdSongs.map {
+ totalDurationInSeconds += $0.duration
+ return Song(mpdSong: $0)
+ }
+ )
+
+ let totalDuration = Time(timeInSeconds: totalDurationInSeconds)
+
+ self.albumTracksView.reloadData()
+
+ guard let mpdSong = album.mpdAlbum.firstSong
+ else { return }
+
+ self.getBigCoverArt(song: Song(mpdSong: mpdSong), album: album)
+
+ var metadata = "\(mpdSongs.count) song"
+ metadata += mpdSongs.count > 1 ? "s," : ","
+ if totalDuration.hours > 0 {
+ metadata += " \(totalDuration.hours) hour"
+ metadata += totalDuration.hours > 1 ? "s" : ""
+ }
+ metadata += " \(totalDuration.minutes) minute"
+ metadata += totalDuration.minutes > 1 ? "s" : ""
+ self.songListMetadata.text = metadata
+ }
+ }
+ }
+
+ func getBigCoverArt(song: Song, album: Album) {
+ let provider = MPDAlbumArtImageDataProvider(
+ songUri: song.mpdSong.uriString,
+ cacheKey: album.hash
+ )
+
+ albumCoverView.kf.setImage(
+ with: .provider(provider),
+ placeholder: UIImage(named: "defaultCoverArt"),
+ options: [
+ .processor(DownsamplingImageProcessor(size: .albumDetailCoverSize)),
+ .scaleFactor(traitCollection.displayScale),
+ ]
+ )
+ }
+
+ override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
+ super.traitCollectionDidChange(previousTraitCollection)
+
+ if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
+ setAppearance()
+ }
+ }
+
+ func setAppearance() {
+ let darkMode = traitCollection.userInterfaceStyle == .dark
+
+ albumCoverView.layer.borderColor = darkMode ? CGColor.albumBorderColorDark : CGColor.albumBorderColorLight
+ }
+
+ @IBAction func playAlbumAction(_ sender: Any) {
+ guard let album = album else { return }
+
+ App.mpdClient.playAlbum(album.mpdAlbum)
+ }
+
+ @IBAction func addAlbumToQueueAction(_ sender: Any) {
+ guard let album = album else { return }
+
+ let queueLength = App.store.state.queueState.queue.count
+
+ App.mpdClient.addAlbumToQueue(album: album.mpdAlbum, at: queueLength)
+ }
+
+ @IBOutlet var songListMetadata: UILabel!
+
+ @IBOutlet var addAlbumToQueueButton: UIButton!
+ @IBOutlet var playAlbumButton: UIButton!
+ @IBOutlet var albumTitle: UILabel!
+ @IBOutlet var albumMetadata: UILabel!
+
+ @IBOutlet var albumCoverView: UIImageView!
+ @IBOutlet var albumTracksView: UITableView!
+}
diff --git a/iOS/Components/Browser/Album Detail/AlbumTracksDataSource.swift b/iOS/Components/Browser/Album Detail/AlbumTracksDataSource.swift
new file mode 100644
index 0000000..b12725c
--- /dev/null
+++ b/iOS/Components/Browser/Album Detail/AlbumTracksDataSource.swift
@@ -0,0 +1,52 @@
+//
+// AlbumTracksDataSource.swift
+// Persephone
+//
+// Created by Daniel Barber on 2019/5/19.
+// Copyright © 2019 Dan Barber. All rights reserved.
+//
+
+import UIKit
+
+class AlbumTracksDataSource: NSObject, UITableViewDataSource {
+ var albumSongs: [AlbumSongItem] = []
+
+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+ return albumSongs.count
+ }
+
+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+ let albumSongItem = albumSongs[indexPath.row]
+
+ if let song = albumSongItem.song {
+ guard let albumSongCell = tableView.dequeueReusableCell(withIdentifier: "albumSongCell") as? AlbumSongCell
+ else { return AlbumSongCell() }
+
+ albumSongCell.setSongItem(songItem: albumSongItem)
+
+ return albumSongCell
+ } else if let disc = albumSongItem.disc {
+ guard let albumDiscCell = tableView.dequeueReusableCell(withIdentifier: "albumDiscCell") as? AlbumDiscCell
+ else { return AlbumDiscCell() }
+
+ albumDiscCell.setSongItem(songItem: albumSongItem)
+
+ return albumDiscCell
+ }
+
+ return UITableViewCell()
+ }
+
+ func setAlbumSongs(_ songs: [Song]) {
+ var disc: String? = ""
+
+ songs.forEach { song in
+ if song.disc != disc && song.disc != "0" {
+ disc = song.disc
+ albumSongs.append(AlbumSongItem(disc: song.disc))
+ }
+
+ albumSongs.append(AlbumSongItem(song: song))
+ }
+ }
+}
diff --git a/iOS/Components/Now Playing/NowPlayingBarViewController.swift b/iOS/Components/Now Playing/NowPlayingBarViewController.swift
new file mode 100644
index 0000000..b41a81b
--- /dev/null
+++ b/iOS/Components/Now Playing/NowPlayingBarViewController.swift
@@ -0,0 +1,134 @@
+//
+// NowPlayingViewController.swift
+// Persephone-iOS
+//
+// Created by Dan Barber on 2020-5-15.
+// Copyright © 2020 Dan Barber. All rights reserved.
+//
+
+import UIKit
+import ReSwift
+import Kingfisher
+
+class NowPlayingBarViewController: UIViewController {
+ @IBOutlet var separatorHeight: NSLayoutConstraint!
+ @IBOutlet var playPauseButton: UIButton!
+ @IBOutlet var nextButton: UIButton!
+ @IBOutlet var songTitle: UILabel!
+ @IBOutlet var albumCoverView: UIImageView!
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ separatorHeight.constant = 1 / traitCollection.displayScale
+
+ App.store.subscribe(self) {
+ $0.select {
+ ($0.playerState, $0.queueState)
+ }
+ }
+
+ NotificationCenter.default.addObserver(self, selector: #selector(didConnect), name: .didConnect, object: nil)
+ NotificationCenter.default.addObserver(self, selector: #selector(willDisconnect), name: .willDisconnect, object: nil)
+ NotificationCenter.default.addObserver(self, selector: #selector(didReloadAlbumArt), name: .didReloadAlbumArt, object: nil)
+
+ albumCoverView.layer.backgroundColor = UIColor.black.cgColor
+ albumCoverView.layer.cornerRadius = 4
+ albumCoverView.layer.borderWidth = 1 / traitCollection.displayScale
+ albumCoverView.layer.masksToBounds = true
+
+ setAppearance()
+ }
+
+ override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
+ super.traitCollectionDidChange(previousTraitCollection)
+
+ separatorHeight.constant = 1 / traitCollection.displayScale
+
+ if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) {
+ setAppearance()
+ }
+ }
+
+ func setAppearance() {
+ let darkMode = traitCollection.userInterfaceStyle == .dark
+
+ albumCoverView.layer.borderColor = darkMode ? CGColor.albumBorderColorDark : CGColor.albumBorderColorLight
+ }
+
+ @objc func didConnect() {
+ App.mpdClient.fetchQueue()
+ }
+
+ @objc func willDisconnect() {
+ DispatchQueue.main.async {
+ App.store.dispatch(UpdateQueuePosAction(queuePos: -1))
+ App.store.dispatch(UpdateQueueAction(queue: []))
+ }
+ }
+
+ @objc func didReloadAlbumArt() {
+ // NO-OP
+ }
+
+ func setTransportControlState(_ state: PlayerState) {
+ guard let state = state.state else { return }
+
+ playPauseButton.isEnabled = state.isOneOf([.playing, .paused, .stopped])
+ nextButton.isEnabled = state.isOneOf([.playing, .paused])
+
+ if state.isOneOf([.paused, .stopped, .unknown]) {
+ playPauseButton.setImage(.playIconLarge, for: .normal)
+ } else {
+ playPauseButton.setImage(.pauseIconLarge, for: .normal)
+ }
+ }
+
+ func setSong(_ song: Song?) {
+ guard let song = song else {
+ self.songTitle.text = "Not Playing"
+ self.albumCoverView.image = .defaultCoverArt
+ return
+ }
+
+ songTitle.text = song.title
+
+ let provider = MPDAlbumArtImageDataProvider(
+ songUri: song.mpdSong.uriString,
+ cacheKey: song.album.hash
+ )
+
+ albumCoverView.kf.setImage(
+ with: .provider(provider),
+ placeholder: UIImage.defaultCoverArt,
+ options: [
+ .processor(DownsamplingImageProcessor(size: .queueSongCoverSize)),
+ .scaleFactor(traitCollection.displayScale),
+ ]
+ )
+ }
+
+ @IBAction func playPauseButtonAction(_ sender: Any) {
+ App.mpdClient.playPause()
+ }
+
+ @IBAction func nextButtonAction(_ sender: Any) {
+ App.mpdClient.nextTrack()
+ }
+
+ @IBAction func expandNowPlaying(_ sender: Any) {
+ }
+}
+
+extension NowPlayingBarViewController: StoreSubscriber {
+ typealias StoreSubscriberStateType = (
+ playerState: PlayerState, queueState: QueueState
+ )
+
+ func newState(state: StoreSubscriberStateType) {
+ DispatchQueue.main.async {
+ self.setTransportControlState(state.playerState)
+ self.setSong(state.playerState.currentSong)
+ }
+ }
+}
diff --git a/iOS/Components/Now Playing/NowPlayingBarViewController.xib b/iOS/Components/Now Playing/NowPlayingBarViewController.xib
new file mode 100644
index 0000000..3078075
--- /dev/null
+++ b/iOS/Components/Now Playing/NowPlayingBarViewController.xib
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/iOS/Components/Now Playing/NowPlayingTabBar.swift b/iOS/Components/Now Playing/NowPlayingTabBar.swift
new file mode 100644
index 0000000..7a40209
--- /dev/null
+++ b/iOS/Components/Now Playing/NowPlayingTabBar.swift
@@ -0,0 +1,47 @@
+//
+// NowPlayingTabBar.swift
+// Persephone-iOS
+//
+// Created by Dan Barber on 2020-6-12.
+// Copyright © 2020 Dan Barber. All rights reserved.
+//
+
+import UIKit
+
+class NowPlayingControlBackground: UIControl {
+// override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
+//
+// }
+//
+// override func cancelTracking(with event: UIEvent?) {
+//
+// }
+//
+// override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
+//
+// // if touch is inside your control
+// sendActions(for: .touchUpInside)
+// }
+}
+
+class NowPlayingTabBar: UITabBar {
+ static let barHeight: CGFloat = 56
+
+ override func awakeFromNib() {
+ super.awakeFromNib()
+
+ let bounds = CGRect(x: 0, y: 0, width: 1, height: 1)
+ NowPlayingTabBar.appearance().shadowImage = UIGraphicsImageRenderer(bounds: bounds).image { context in
+ UIColor.systemRed.setFill()
+ context.fill(bounds)
+ }
+ }
+
+ override func layoutSubviews() {
+ super.layoutSubviews()
+
+ for case let control as UIControl in subviews {
+ control.frame.origin.y += Self.barHeight
+ }
+ }
+}
diff --git a/iOS/Components/Now Playing/NowPlayingTabBarController.swift b/iOS/Components/Now Playing/NowPlayingTabBarController.swift
new file mode 100644
index 0000000..c446de8
--- /dev/null
+++ b/iOS/Components/Now Playing/NowPlayingTabBarController.swift
@@ -0,0 +1,30 @@
+//
+// NowPlayingTabBarController.swift
+// Persephone
+//
+// Created by Dan Barber on 2020-6-12.
+// Copyright © 2020 Dan Barber. All rights reserved.
+//
+
+import UIKit
+
+class NowPlayingTabBarController: UITabBarController {
+ let nowPlayingViewController = NowPlayingBarViewController()
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ let subview = nowPlayingViewController.view!
+ view.addSubview(subview)
+ subview.translatesAutoresizingMaskIntoConstraints = false
+
+ NSLayoutConstraint.activate([
+ subview.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ subview.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ subview.topAnchor.constraint(equalTo: tabBar.topAnchor),
+ subview.heightAnchor.constraint(equalToConstant: NowPlayingTabBar.barHeight),
+ ])
+
+ additionalSafeAreaInsets.bottom = NowPlayingTabBar.barHeight
+ }
+}
diff --git a/iOS/Components/Now Playing/NowPlayingViewController.swift b/iOS/Components/Now Playing/NowPlayingViewController.swift
new file mode 100644
index 0000000..d1ff710
--- /dev/null
+++ b/iOS/Components/Now Playing/NowPlayingViewController.swift
@@ -0,0 +1,30 @@
+//
+// NowPlayingViewController.swift
+// Persephone-iOS
+//
+// Created by Dan Barber on 2020-12-01.
+// Copyright © 2020 Dan Barber. All rights reserved.
+//
+
+import UIKit
+
+class NowPlayingViewController: UIViewController {
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+
+ // Do any additional setup after loading the view.
+ }
+
+
+ /*
+ // MARK: - Navigation
+
+ // In a storyboard-based application, you will often want to do a little preparation before navigation
+ override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+ // Get the new view controller using segue.destination.
+ // Pass the selected object to the new view controller.
+ }
+ */
+
+}
diff --git a/iOS/Components/Now Playing/NowPlayingViewController.xib b/iOS/Components/Now Playing/NowPlayingViewController.xib
new file mode 100644
index 0000000..68fb285
--- /dev/null
+++ b/iOS/Components/Now Playing/NowPlayingViewController.xib
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/iOS/Components/Shared/Extensions/CGColor.swift b/iOS/Components/Shared/Extensions/CGColor.swift
new file mode 100644
index 0000000..3e296b1
--- /dev/null
+++ b/iOS/Components/Shared/Extensions/CGColor.swift
@@ -0,0 +1,14 @@
+//
+// NSColor.swift
+// Persephone
+//
+// Created by Daniel Barber on 2019/2/16.
+// Copyright © 2019 Dan Barber. All rights reserved.
+//
+
+import CoreGraphics
+
+extension CGColor {
+ static let albumBorderColorLight = CGColor.init(srgbRed: 0, green: 0, blue: 0, alpha: 0.15)
+ static let albumBorderColorDark = CGColor.init(srgbRed: 255, green: 255, blue: 255, alpha: 0.15)
+}
diff --git a/iOS/Components/Shared/Extensions/CGSize.swift b/iOS/Components/Shared/Extensions/CGSize.swift
new file mode 100644
index 0000000..82bd7f8
--- /dev/null
+++ b/iOS/Components/Shared/Extensions/CGSize.swift
@@ -0,0 +1,20 @@
+//
+// CGSize.swift
+// Persephone
+//
+// Created by Daniel Barber on 1/20/20.
+// Copyright © 2020 Dan Barber. All rights reserved.
+//
+
+import UIKit
+
+extension CGSize {
+ static let albumListWidth = (UIScreen.main.bounds.width - 60) / 2
+ static let albumDetailWidth = UIScreen.main.bounds.width - 40
+
+ static let queueSongCoverSize = CGSize(width: 32, height: 32)
+ static let albumListCoverSize = CGSize(width: albumListWidth, height: albumListWidth)
+ static let albumDetailCoverSize = CGSize(width: albumDetailWidth, height: albumDetailWidth)
+ static let currentlyPlayingCoverSize = albumDetailCoverSize
+ static let notificationCoverSize = albumListCoverSize
+}
diff --git a/iOS/Components/Shared/Extensions/UIFont.swift b/iOS/Components/Shared/Extensions/UIFont.swift
new file mode 100644
index 0000000..ee12806
--- /dev/null
+++ b/iOS/Components/Shared/Extensions/UIFont.swift
@@ -0,0 +1,13 @@
+//
+// UIFont.swift
+// Persephone-iOS
+//
+// Created by Daniel Barber on 2020-3-31.
+// Copyright © 2020 Dan Barber. All rights reserved.
+//
+
+import UIKit
+
+extension UIFont {
+ static let timerFont = monospacedDigitSystemFont(ofSize: 16, weight: .regular)
+}
diff --git a/iOS/Components/Shared/Extensions/UIImage.swift b/iOS/Components/Shared/Extensions/UIImage.swift
new file mode 100644
index 0000000..ea3b955
--- /dev/null
+++ b/iOS/Components/Shared/Extensions/UIImage.swift
@@ -0,0 +1,15 @@
+//
+// UIImage.swift
+// Persephone-iOS
+//
+// Created by Dan Barber on 2020-6-18.
+// Copyright © 2020 Dan Barber. All rights reserved.
+//
+
+import UIKit
+
+extension UIImage {
+ static let defaultCoverArt = UIImage(named: "defaultCoverArt")
+ static let playIconLarge = UIImage(named: "playButtonLarge")
+ static let pauseIconLarge = UIImage(named: "pauseButtonLarge")
+}
diff --git a/iOS/SceneDelegate.swift b/iOS/SceneDelegate.swift
new file mode 100644
index 0000000..a601459
--- /dev/null
+++ b/iOS/SceneDelegate.swift
@@ -0,0 +1,59 @@
+//
+// SceneDelegate.swift
+// Persephone-iOS
+//
+// Created by Daniel Barber on 2020-3-13.
+// Copyright © 2020 Dan Barber. All rights reserved.
+//
+
+import UIKit
+
+class SceneDelegate: UIResponder, UIWindowSceneDelegate {
+
+ var window: UIWindow?
+
+
+ func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
+ // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
+ // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
+ // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
+ guard let _ = (scene as? UIWindowScene) else { return }
+
+ _ = App.mpdServerController
+
+ App.store.dispatch(UpdateServerHost(host: "192.168.4.31"))
+ }
+
+ func sceneDidDisconnect(_ scene: UIScene) {
+ // Called as the scene is being released by the system.
+ // This occurs shortly after the scene enters the background, or when its session is discarded.
+ // Release any resources associated with this scene that can be re-created the next time the scene connects.
+ // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
+ App.mpdServerController.disconnect()
+ }
+
+ func sceneDidBecomeActive(_ scene: UIScene) {
+ // Called when the scene has moved from an inactive state to an active state.
+ // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
+ App.mpdServerController.connect()
+ }
+
+ func sceneWillResignActive(_ scene: UIScene) {
+ // Called when the scene will move from an active state to an inactive state.
+ // This may occur due to temporary interruptions (ex. an incoming phone call).
+ }
+
+ func sceneWillEnterForeground(_ scene: UIScene) {
+ // Called as the scene transitions from the background to the foreground.
+ // Use this method to undo the changes made on entering the background.
+ }
+
+ func sceneDidEnterBackground(_ scene: UIScene) {
+ // Called as the scene transitions from the foreground to the background.
+ // Use this method to save data, release shared resources, and store enough scene-specific state information
+ // to restore the scene back to its current state.
+ }
+
+
+}
+