1
1
mirror of https://github.com/danbee/persephone synced 2025-03-04 08:39:11 +00:00

Compare commits

...

18 Commits

Author SHA1 Message Date
17e592ef86
Add dynamic dock menu with "Now Playing" section
Inspired pretty heavily by iTunes.
2019-04-29 22:44:34 -04:00
ef8971f2fe
WIP: Dock menu 2019-04-29 19:36:05 -04:00
4d2a8087ed
Import AppKit rather than Cocoa
Importing Cocoa pulls in CoreData, which we're not using.

https://github.com/brentsimmons/NetNewsWire/blob/master/Technotes/CodingGuidelines.md
2019-04-29 08:59:04 -04:00
b311005393
We need to invalidate the timer on the main thread 2019-04-29 08:31:58 -04:00
0de001ce16
Give the queue its own local state 2019-04-29 08:31:44 -04:00
22bb7efef2
Completely rejigger preferences system to use ReSwift 2019-04-28 18:02:09 -04:00
8882c4246c
Refactor all the things!
I discovered Xcode's refactor function 😁
2019-04-28 10:48:10 -04:00
d2d4705e87
Current artwork and notifications 2019-04-27 22:39:18 -04:00
63afa3ffce
Fix album reloading issue
All the albums were being forced to reload on update because they come
back with `unLoaded` cover art.
2019-04-27 21:14:59 -04:00
6e0c97492b
Some tweaks 2019-04-27 20:48:49 -04:00
86feface73
Move ReSwift stuff into extensions 2019-04-27 20:48:49 -04:00
81b0626017
Move database updating indicator to ReSwift 2019-04-27 20:48:49 -04:00
5f80a1b8e9
Reimplement reset album art 2019-04-27 20:48:49 -04:00
40ae6909cd
Album images load! 2019-04-27 20:48:49 -04:00
2e6c903d74
Mostly working, albumlist is not showing album art 2019-04-27 20:48:48 -04:00
bb5a7c2c5c
Transport and progress finished 2019-04-27 20:45:27 -04:00
d076ef311b
We need to run the timer on the main thread
This adds it to the main RunLoop.
2019-04-27 20:45:26 -04:00
5651276bd6
WIP: Big ReSwift refactor 2019-04-27 20:45:26 -04:00
64 changed files with 1244 additions and 586 deletions

View File

@ -2,3 +2,5 @@ github "SwiftyJSON/SwiftyJSON" ~> 4.0
github "PromiseKit/Foundation" ~> 3.0 github "PromiseKit/Foundation" ~> 3.0
github "nhurden/MediaKeyTap" github "nhurden/MediaKeyTap"
github "krzyzanowskim/CryptoSwift" github "krzyzanowskim/CryptoSwift"
github "ReSwift/ReSwift" "mjarvis/swift-5.0"
github "tonyarnold/Differ"

View File

@ -1,5 +1,7 @@
github "PromiseKit/Foundation" "3.3.2" github "PromiseKit/Foundation" "3.3.2"
github "SwiftyJSON/SwiftyJSON" "4.2.0" github "ReSwift/ReSwift" "bc5943ad9493fc7fbad5200f690be538db8e86fb"
github "SwiftyJSON/SwiftyJSON" "4.3.0"
github "krzyzanowskim/CryptoSwift" "1.0.0" github "krzyzanowskim/CryptoSwift" "1.0.0"
github "mxcl/PromiseKit" "6.8.4" github "mxcl/PromiseKit" "6.8.4"
github "nhurden/MediaKeyTap" "2.2.1" github "nhurden/MediaKeyTap" "2.2.1"
github "tonyarnold/Differ" "1.4.2"

View File

@ -17,11 +17,8 @@
E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */; }; E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */; };
E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3C1220E134F0006D9BE /* AlbumViewController.swift */; }; E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3C1220E134F0006D9BE /* AlbumViewController.swift */; };
E408D3CB220E341D0006D9BE /* AlbumViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = E408D3C9220E341D0006D9BE /* AlbumViewItem.xib */; }; E408D3CB220E341D0006D9BE /* AlbumViewItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = E408D3C9220E341D0006D9BE /* AlbumViewItem.xib */; };
E40F41F3221EDE27004B6CB8 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40F41F2221EDE27004B6CB8 /* Preferences.swift */; };
E40FE71B221B904300A4223F /* NSEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40FE71A221B904300A4223F /* NSEvent.swift */; }; E40FE71B221B904300A4223F /* NSEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = E40FE71A221B904300A4223F /* NSEvent.swift */; };
E419E2872249B96600216A8C /* Song.swift in Sources */ = {isa = PBXBuildFile; fileRef = E419E2862249B96600216A8C /* Song.swift */; }; E419E2872249B96600216A8C /* Song.swift in Sources */ = {isa = PBXBuildFile; fileRef = E419E2862249B96600216A8C /* Song.swift */; };
E41B22C021FB6BBA00D544F6 /* libmpdclient.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (Required, ); }; };
E41B22C121FB6C3300D544F6 /* libmpdclient.2.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41B22C521FB932700D544F6 /* MPDClient.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 */; }; E41E52FD223BF87300173814 /* MPDClient+Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E52FC223BF87300173814 /* MPDClient+Connection.swift */; };
E41E52FF223BF95E00173814 /* MPDClient+Transport.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E52FE223BF95E00173814 /* MPDClient+Transport.swift */; }; E41E52FF223BF95E00173814 /* MPDClient+Transport.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E52FE223BF95E00173814 /* MPDClient+Transport.swift */; };
@ -31,38 +28,28 @@
E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5306223C019100173814 /* MPDClient+Status.swift */; }; E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5306223C019100173814 /* MPDClient+Status.swift */; };
E41E5309223C020400173814 /* MPDClient+Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5308223C020400173814 /* MPDClient+Command.swift */; }; E41E5309223C020400173814 /* MPDClient+Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5308223C020400173814 /* MPDClient+Command.swift */; };
E41E530B223C033700173814 /* MPDClient+Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E530A223C033700173814 /* MPDClient+Album.swift */; }; E41E530B223C033700173814 /* MPDClient+Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E530A223C033700173814 /* MPDClient+Album.swift */; };
E41E530E223EF4CF00173814 /* AlbumArtService+Caching.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E530D223EF4CF00173814 /* AlbumArtService+Caching.swift */; }; E41E530E223EF4CF00173814 /* CoverArtService+Caching.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E530D223EF4CF00173814 /* CoverArtService+Caching.swift */; };
E41E5310223EF6CE00173814 /* AlbumArtService+Remote.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E530F223EF6CE00173814 /* AlbumArtService+Remote.swift */; }; E41E5310223EF6CE00173814 /* CoverArtService+Remote.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E530F223EF6CE00173814 /* CoverArtService+Remote.swift */; };
E41E5312223EF74A00173814 /* AlbumArtService+Filesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5311223EF74A00173814 /* AlbumArtService+Filesystem.swift */; }; E41E5312223EF74A00173814 /* CoverArtService+Filesystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41E5311223EF74A00173814 /* CoverArtService+Filesystem.swift */; };
E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */; }; E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */; };
E421ACA3221F73C4008B2449 /* MediaKeyTap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E421ACA1221F73B8008B2449 /* MediaKeyTap.framework */; };
E421ACA4221F73C4008B2449 /* MediaKeyTap.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E421ACA1221F73B8008B2449 /* MediaKeyTap.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42410B52241B956005ED6DF /* MPDClient+Database.swift */; }; E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = E42410B52241B956005ED6DF /* MPDClient+Database.swift */; };
E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3922176D6400A13ED9 /* LICENSE.md */; }; E42A8F3B22176D6400A13ED9 /* LICENSE.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3922176D6400A13ED9 /* LICENSE.md */; };
E42A8F3C22176D6400A13ED9 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3A22176D6400A13ED9 /* README.md */; }; E42A8F3C22176D6400A13ED9 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = E42A8F3A22176D6400A13ED9 /* README.md */; };
E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435E3E1221CD4E200184CFC /* NSFont.swift */; }; E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435E3E1221CD4E200184CFC /* NSFont.swift */; };
E435E3E4221CD75D00184CFC /* NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435E3E3221CD75D00184CFC /* NSImage.swift */; }; E435E3E4221CD75D00184CFC /* NSImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E435E3E3221CD75D00184CFC /* NSImage.swift */; };
E439109822640213002982E9 /* SongNotifierService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E439109722640213002982E9 /* SongNotifierService.swift */; }; E439109822640213002982E9 /* SongNotifierService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E439109722640213002982E9 /* SongNotifierService.swift */; };
E4405192227644340090CD6F /* MPDServerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4405191227644340090CD6F /* MPDServerController.swift */; };
E450AD7E222620A10091BED3 /* Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD7D222620A10091BED3 /* Album.swift */; }; E450AD7E222620A10091BED3 /* Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD7D222620A10091BED3 /* Album.swift */; };
E450AD8622262AE60091BED3 /* SwiftyJSON.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E450AD8522262AE60091BED3 /* SwiftyJSON.framework */; };
E450AD8822262AEC0091BED3 /* SwiftyJSON.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E450AD8522262AE60091BED3 /* SwiftyJSON.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E450AD8F22262C620091BED3 /* PromiseKit.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD8E22262C620091BED3 /* PromiseKit.framework.dSYM */; }; E450AD8F22262C620091BED3 /* PromiseKit.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD8E22262C620091BED3 /* PromiseKit.framework.dSYM */; };
E450AD9122262C780091BED3 /* SwiftyJSON.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD9022262C780091BED3 /* SwiftyJSON.framework.dSYM */; }; E450AD9122262C780091BED3 /* SwiftyJSON.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD9022262C780091BED3 /* SwiftyJSON.framework.dSYM */; };
E450AD9222262C970091BED3 /* PromiseKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E450AD8C22262C590091BED3 /* PromiseKit.framework */; }; E450AD9522262DF10091BED3 /* CoverArtQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD9422262DF10091BED3 /* CoverArtQueue.swift */; };
E450AD9322262C970091BED3 /* PromiseKit.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E450AD8C22262C590091BED3 /* PromiseKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E450AD9522262DF10091BED3 /* AlbumArtQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD9422262DF10091BED3 /* AlbumArtQueue.swift */; };
E450AD98222633920091BED3 /* Alamofire.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD96222633920091BED3 /* Alamofire.framework.dSYM */; }; E450AD98222633920091BED3 /* Alamofire.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD96222633920091BED3 /* Alamofire.framework.dSYM */; };
E450ADA12229E7C90091BED3 /* PMKFoundation.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD9F2229E7C90091BED3 /* PMKFoundation.framework.dSYM */; }; E450ADA12229E7C90091BED3 /* PMKFoundation.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD9F2229E7C90091BED3 /* PMKFoundation.framework.dSYM */; };
E450ADA32229E7E00091BED3 /* PMKFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E450ADA02229E7C90091BED3 /* PMKFoundation.framework */; };
E450ADA42229E7E00091BED3 /* PMKFoundation.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E450ADA02229E7C90091BED3 /* PMKFoundation.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E45962C62241A78500FC1A1E /* MPDCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45962C52241A78500FC1A1E /* MPDCommand.swift */; }; E45962C62241A78500FC1A1E /* MPDCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45962C52241A78500FC1A1E /* MPDCommand.swift */; };
E45E4FDA22515D87004B537F /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = E45E4FD722515D87004B537F /* CHANGELOG.md */; }; E45E4FDA22515D87004B537F /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = E45E4FD722515D87004B537F /* CHANGELOG.md */; };
E45E4FDB22515D87004B537F /* Brewfile in Resources */ = {isa = PBXBuildFile; fileRef = E45E4FD822515D87004B537F /* Brewfile */; }; E45E4FDB22515D87004B537F /* Brewfile in Resources */ = {isa = PBXBuildFile; fileRef = E45E4FD822515D87004B537F /* Brewfile */; };
E45E4FDC22515D87004B537F /* Cartfile in Resources */ = {isa = PBXBuildFile; fileRef = E45E4FD922515D87004B537F /* Cartfile */; }; E45E4FDC22515D87004B537F /* Cartfile in Resources */ = {isa = PBXBuildFile; fileRef = E45E4FD922515D87004B537F /* Cartfile */; };
E45E4FDF225168DA004B537F /* CryptoSwift.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E45E4FDD225168DA004B537F /* CryptoSwift.framework.dSYM */; }; E45E4FDF225168DA004B537F /* CryptoSwift.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E45E4FDD225168DA004B537F /* CryptoSwift.framework.dSYM */; };
E45E4FE0225168DA004B537F /* CryptoSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E45E4FDE225168DA004B537F /* CryptoSwift.framework */; };
E45E4FE122516953004B537F /* CryptoSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E45E4FDE225168DA004B537F /* CryptoSwift.framework */; };
E45E4FE222516953004B537F /* CryptoSwift.framework in Embed Libraries */ = {isa = PBXBuildFile; fileRef = E45E4FDE225168DA004B537F /* CryptoSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E465049A21E94DF500A70F4C /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E465049921E94DF500A70F4C /* WindowController.swift */; }; E465049A21E94DF500A70F4C /* WindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E465049921E94DF500A70F4C /* WindowController.swift */; };
E47E2FCC2220573500F747E6 /* MediaKeyTap.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E47E2FCB2220573500F747E6 /* MediaKeyTap.framework.dSYM */; }; E47E2FCC2220573500F747E6 /* MediaKeyTap.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E47E2FCB2220573500F747E6 /* MediaKeyTap.framework.dSYM */; };
E47E2FD122205C4600F747E6 /* MainSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */; }; E47E2FD122205C4600F747E6 /* MainSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */; };
@ -73,9 +60,43 @@
E47E2FE52220AA0700F747E6 /* AlbumViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FE42220AA0700F747E6 /* AlbumViewLayout.swift */; }; E47E2FE52220AA0700F747E6 /* AlbumViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FE42220AA0700F747E6 /* AlbumViewLayout.swift */; };
E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4928E0A2218D62A001D4BEA /* CGColor.swift */; }; E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4928E0A2218D62A001D4BEA /* CGColor.swift */; };
E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* MPDStatus.swift */; }; E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* MPDStatus.swift */; };
E4A83BEF2221F8CF0098FED6 /* AlbumArtPrefsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BEE2221F8CF0098FED6 /* AlbumArtPrefsController.swift */; }; E4A83BEF2221F8CF0098FED6 /* CoverArtPrefsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */; };
E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */; }; E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */; };
E4A83BF4222207D50098FED6 /* AlbumArtService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF3222207D50098FED6 /* AlbumArtService.swift */; }; E4A83BF4222207D50098FED6 /* CoverArtService.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF3222207D50098FED6 /* CoverArtService.swift */; };
E4B11B53226928F20075461B /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B52226928F20075461B /* AppState.swift */; };
E4B11B5A2269296C0075461B /* ReSwift.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E4B11B582269296C0075461B /* ReSwift.framework.dSYM */; };
E4B11B61226A4C000075461B /* PlayerReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B60226A4BFF0075461B /* PlayerReducer.swift */; };
E4B11B63226A4C510075461B /* AppReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B62226A4C510075461B /* AppReducer.swift */; };
E4B11B66226A4F830075461B /* PlayerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B65226A4F830075461B /* PlayerState.swift */; };
E4B11B68226A4FA00075461B /* QueueState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B67226A4FA00075461B /* QueueState.swift */; };
E4B11B6A226A4FBC0075461B /* AlbumListState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B69226A4FBC0075461B /* AlbumListState.swift */; };
E4B11B73226A6C770075461B /* TrackTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B72226A6C770075461B /* TrackTimer.swift */; };
E4B11B75226CC4D30075461B /* QueueReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B74226CC4D30075461B /* QueueReducer.swift */; };
E4B11B79226D346B0075461B /* AlbumListReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11B78226D346B0075461B /* AlbumListReducer.swift */; };
E4B11B7E2274E36D0075461B /* Differ.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E4B11B7C2274E36D0075461B /* Differ.framework.dSYM */; };
E4B11B962274E43B0075461B /* Differ.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4B11B7D2274E36D0075461B /* Differ.framework */; };
E4B11B972274E43B0075461B /* Differ.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E4B11B7D2274E36D0075461B /* Differ.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E4B11B982274E43B0075461B /* ReSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4B11B572269296C0075461B /* ReSwift.framework */; };
E4B11B992274E43B0075461B /* ReSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E4B11B572269296C0075461B /* ReSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E4B11B9A2274E43B0075461B /* CryptoSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E45E4FDE225168DA004B537F /* CryptoSwift.framework */; };
E4B11B9B2274E43B0075461B /* CryptoSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E45E4FDE225168DA004B537F /* CryptoSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E4B11B9C2274E43B0075461B /* PMKFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E450ADA02229E7C90091BED3 /* PMKFoundation.framework */; };
E4B11B9D2274E43B0075461B /* PMKFoundation.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E450ADA02229E7C90091BED3 /* PMKFoundation.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E4B11B9E2274E43B0075461B /* PromiseKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E450AD8C22262C590091BED3 /* PromiseKit.framework */; };
E4B11B9F2274E43B0075461B /* PromiseKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E450AD8C22262C590091BED3 /* PromiseKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E4B11BA02274E43B0075461B /* SwiftyJSON.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E450AD8522262AE60091BED3 /* SwiftyJSON.framework */; };
E4B11BA12274E43B0075461B /* SwiftyJSON.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E450AD8522262AE60091BED3 /* SwiftyJSON.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E4B11BA22274E43B0075461B /* MediaKeyTap.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E421ACA1221F73B8008B2449 /* MediaKeyTap.framework */; };
E4B11BA32274E43B0075461B /* MediaKeyTap.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E421ACA1221F73B8008B2449 /* MediaKeyTap.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
E4B11BA62274E44A0075461B /* libmpdclient.2.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
E4B11BA72274E4500075461B /* libmpdclient.2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */; };
E4B11BA92274EDE30075461B /* Loading.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BA82274EDE30075461B /* Loading.swift */; };
E4B11BB02274F71A0075461B /* EnumEquatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BAF2274F71A0075461B /* EnumEquatable.swift */; };
E4B11BB62275374B0075461B /* UserNotificationsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BB52275374B0075461B /* UserNotificationsController.swift */; };
E4B11BB8227538FA0075461B /* CurrentCoverArtView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4B11BB7227538FA0075461B /* CurrentCoverArtView.swift */; };
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 */; };
E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */; }; E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */; };
E4C8B53E22349002009A20F3 /* MPDIdle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53D22349002009A20F3 /* MPDIdle.swift */; }; E4C8B53E22349002009A20F3 /* MPDIdle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4C8B53D22349002009A20F3 /* MPDIdle.swift */; };
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC8F2204EC7F0024217A /* Delegate.swift */; }; E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC8F2204EC7F0024217A /* Delegate.swift */; };
@ -87,6 +108,10 @@
E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */; }; E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */; };
E4F6B463221E125900ACF42A /* QueueItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B462221E125900ACF42A /* QueueItem.swift */; }; E4F6B463221E125900ACF42A /* QueueItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B462221E125900ACF42A /* QueueItem.swift */; };
E4F6B467221E233200ACF42A /* AlbumDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B466221E233200ACF42A /* AlbumDataSource.swift */; }; E4F6B467221E233200ACF42A /* AlbumDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B466221E233200ACF42A /* AlbumDataSource.swift */; };
E4FF718E2276010E00D4C412 /* PreferencesState.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FF718D2276010E00D4C412 /* PreferencesState.swift */; };
E4FF7190227601B400D4C412 /* PreferencesReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FF718F227601B400D4C412 /* PreferencesReducer.swift */; };
E4FF71922276029000D4C412 /* PreferencesActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FF71912276029000D4C412 /* PreferencesActions.swift */; };
E4FF71942276043A00D4C412 /* MPDServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FF71932276043A00D4C412 /* MPDServer.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@ -107,22 +132,6 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
E41B22C221FB6C3300D544F6 /* Embed Libraries */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
E41B22C121FB6C3300D544F6 /* libmpdclient.2.dylib in Embed Libraries */,
E450ADA42229E7E00091BED3 /* PMKFoundation.framework in Embed Libraries */,
E421ACA4221F73C4008B2449 /* MediaKeyTap.framework in Embed Libraries */,
E45E4FE222516953004B537F /* CryptoSwift.framework in Embed Libraries */,
E450AD8822262AEC0091BED3 /* SwiftyJSON.framework in Embed Libraries */,
E450AD9322262C970091BED3 /* PromiseKit.framework in Embed Libraries */,
);
name = "Embed Libraries";
runOnlyForDeploymentPostprocessing = 0;
};
E421AC9B221F7319008B2449 /* CopyFiles */ = { E421AC9B221F7319008B2449 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase; isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
@ -132,6 +141,24 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
E4B11BA52274E43B0075461B /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
E4B11B9B2274E43B0075461B /* CryptoSwift.framework in Embed Frameworks */,
E4B11B972274E43B0075461B /* Differ.framework in Embed Frameworks */,
E4B11BA12274E43B0075461B /* SwiftyJSON.framework in Embed Frameworks */,
E4B11B9F2274E43B0075461B /* PromiseKit.framework in Embed Frameworks */,
E4B11B9D2274E43B0075461B /* PMKFoundation.framework in Embed Frameworks */,
E4B11BA62274E44A0075461B /* libmpdclient.2.dylib in Embed Frameworks */,
E4B11BA32274E43B0075461B /* MediaKeyTap.framework in Embed Frameworks */,
E4B11B992274E43B0075461B /* ReSwift.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
@ -152,7 +179,6 @@
E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawRepresentable.swift; sourceTree = "<group>"; }; E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawRepresentable.swift; sourceTree = "<group>"; };
E408D3C1220E134F0006D9BE /* AlbumViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumViewController.swift; sourceTree = "<group>"; }; E408D3C1220E134F0006D9BE /* AlbumViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumViewController.swift; sourceTree = "<group>"; };
E408D3C9220E341D0006D9BE /* AlbumViewItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlbumViewItem.xib; sourceTree = "<group>"; }; E408D3C9220E341D0006D9BE /* AlbumViewItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlbumViewItem.xib; sourceTree = "<group>"; };
E40F41F2221EDE27004B6CB8 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
E40FE71A221B904300A4223F /* NSEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEvent.swift; sourceTree = "<group>"; }; E40FE71A221B904300A4223F /* NSEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEvent.swift; sourceTree = "<group>"; };
E419E2862249B96600216A8C /* Song.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Song.swift; sourceTree = "<group>"; }; E419E2862249B96600216A8C /* Song.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Song.swift; sourceTree = "<group>"; };
E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libmpdclient.2.dylib; path = libmpdclient/output/libmpdclient.2.dylib; sourceTree = "<group>"; }; E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libmpdclient.2.dylib; path = libmpdclient/output/libmpdclient.2.dylib; sourceTree = "<group>"; };
@ -201,9 +227,9 @@
E41E5306223C019100173814 /* MPDClient+Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Status.swift"; sourceTree = "<group>"; }; E41E5306223C019100173814 /* MPDClient+Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Status.swift"; sourceTree = "<group>"; };
E41E5308223C020400173814 /* MPDClient+Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Command.swift"; sourceTree = "<group>"; }; E41E5308223C020400173814 /* MPDClient+Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Command.swift"; sourceTree = "<group>"; };
E41E530A223C033700173814 /* MPDClient+Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Album.swift"; sourceTree = "<group>"; }; E41E530A223C033700173814 /* MPDClient+Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Album.swift"; sourceTree = "<group>"; };
E41E530D223EF4CF00173814 /* AlbumArtService+Caching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlbumArtService+Caching.swift"; sourceTree = "<group>"; }; E41E530D223EF4CF00173814 /* CoverArtService+Caching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoverArtService+Caching.swift"; sourceTree = "<group>"; };
E41E530F223EF6CE00173814 /* AlbumArtService+Remote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlbumArtService+Remote.swift"; sourceTree = "<group>"; }; E41E530F223EF6CE00173814 /* CoverArtService+Remote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoverArtService+Remote.swift"; sourceTree = "<group>"; };
E41E5311223EF74A00173814 /* AlbumArtService+Filesystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AlbumArtService+Filesystem.swift"; sourceTree = "<group>"; }; E41E5311223EF74A00173814 /* CoverArtService+Filesystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoverArtService+Filesystem.swift"; sourceTree = "<group>"; };
E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPrefsViewController.swift; sourceTree = "<group>"; }; E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralPrefsViewController.swift; sourceTree = "<group>"; };
E421ACA1221F73B8008B2449 /* MediaKeyTap.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaKeyTap.framework; path = Carthage/Build/Mac/MediaKeyTap.framework; sourceTree = "<group>"; }; E421ACA1221F73B8008B2449 /* MediaKeyTap.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaKeyTap.framework; path = Carthage/Build/Mac/MediaKeyTap.framework; sourceTree = "<group>"; };
E42410B52241B956005ED6DF /* MPDClient+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Database.swift"; sourceTree = "<group>"; }; E42410B52241B956005ED6DF /* MPDClient+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Database.swift"; sourceTree = "<group>"; };
@ -212,12 +238,13 @@
E435E3E1221CD4E200184CFC /* NSFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSFont.swift; sourceTree = "<group>"; }; E435E3E1221CD4E200184CFC /* NSFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSFont.swift; sourceTree = "<group>"; };
E435E3E3221CD75D00184CFC /* NSImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImage.swift; sourceTree = "<group>"; }; E435E3E3221CD75D00184CFC /* NSImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSImage.swift; sourceTree = "<group>"; };
E439109722640213002982E9 /* SongNotifierService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongNotifierService.swift; sourceTree = "<group>"; }; E439109722640213002982E9 /* SongNotifierService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongNotifierService.swift; sourceTree = "<group>"; };
E4405191227644340090CD6F /* MPDServerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDServerController.swift; sourceTree = "<group>"; };
E450AD7D222620A10091BED3 /* Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Album.swift; sourceTree = "<group>"; }; E450AD7D222620A10091BED3 /* Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Album.swift; sourceTree = "<group>"; };
E450AD8522262AE60091BED3 /* SwiftyJSON.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyJSON.framework; path = Carthage/Build/Mac/SwiftyJSON.framework; sourceTree = "<group>"; }; E450AD8522262AE60091BED3 /* SwiftyJSON.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyJSON.framework; path = Carthage/Build/Mac/SwiftyJSON.framework; sourceTree = "<group>"; };
E450AD8C22262C590091BED3 /* PromiseKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PromiseKit.framework; path = Carthage/Build/Mac/PromiseKit.framework; sourceTree = "<group>"; }; E450AD8C22262C590091BED3 /* PromiseKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PromiseKit.framework; path = Carthage/Build/Mac/PromiseKit.framework; sourceTree = "<group>"; };
E450AD8E22262C620091BED3 /* PromiseKit.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = PromiseKit.framework.dSYM; path = Carthage/Build/Mac/PromiseKit.framework.dSYM; sourceTree = "<group>"; }; E450AD8E22262C620091BED3 /* PromiseKit.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = PromiseKit.framework.dSYM; path = Carthage/Build/Mac/PromiseKit.framework.dSYM; sourceTree = "<group>"; };
E450AD9022262C780091BED3 /* SwiftyJSON.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = SwiftyJSON.framework.dSYM; path = Carthage/Build/Mac/SwiftyJSON.framework.dSYM; sourceTree = "<group>"; }; E450AD9022262C780091BED3 /* SwiftyJSON.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = SwiftyJSON.framework.dSYM; path = Carthage/Build/Mac/SwiftyJSON.framework.dSYM; sourceTree = "<group>"; };
E450AD9422262DF10091BED3 /* AlbumArtQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumArtQueue.swift; sourceTree = "<group>"; }; E450AD9422262DF10091BED3 /* CoverArtQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverArtQueue.swift; sourceTree = "<group>"; };
E450AD96222633920091BED3 /* Alamofire.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = Alamofire.framework.dSYM; path = Carthage/Build/Mac/Alamofire.framework.dSYM; sourceTree = "<group>"; }; E450AD96222633920091BED3 /* Alamofire.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = Alamofire.framework.dSYM; path = Carthage/Build/Mac/Alamofire.framework.dSYM; sourceTree = "<group>"; };
E450AD97222633920091BED3 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/Mac/Alamofire.framework; sourceTree = "<group>"; }; E450AD97222633920091BED3 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/Mac/Alamofire.framework; sourceTree = "<group>"; };
E450AD9E2229B9BC0091BED3 /* PersephoneBridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PersephoneBridgingHeader.h; sourceTree = "<group>"; }; E450AD9E2229B9BC0091BED3 /* PersephoneBridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PersephoneBridgingHeader.h; sourceTree = "<group>"; };
@ -239,9 +266,29 @@
E47E2FE42220AA0700F747E6 /* AlbumViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumViewLayout.swift; sourceTree = "<group>"; }; E47E2FE42220AA0700F747E6 /* AlbumViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumViewLayout.swift; sourceTree = "<group>"; };
E4928E0A2218D62A001D4BEA /* CGColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGColor.swift; sourceTree = "<group>"; }; E4928E0A2218D62A001D4BEA /* CGColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGColor.swift; sourceTree = "<group>"; };
E4A642D922090CBE00067D21 /* MPDStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDStatus.swift; sourceTree = "<group>"; }; E4A642D922090CBE00067D21 /* MPDStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDStatus.swift; sourceTree = "<group>"; };
E4A83BEE2221F8CF0098FED6 /* AlbumArtPrefsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumArtPrefsController.swift; sourceTree = "<group>"; }; E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverArtPrefsController.swift; sourceTree = "<group>"; };
E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = "<group>"; }; E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = "<group>"; };
E4A83BF3222207D50098FED6 /* AlbumArtService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumArtService.swift; sourceTree = "<group>"; }; E4A83BF3222207D50098FED6 /* CoverArtService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverArtService.swift; sourceTree = "<group>"; };
E4B11B52226928F20075461B /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
E4B11B572269296C0075461B /* ReSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReSwift.framework; path = Carthage/Build/Mac/ReSwift.framework; sourceTree = "<group>"; };
E4B11B582269296C0075461B /* ReSwift.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = ReSwift.framework.dSYM; path = Carthage/Build/Mac/ReSwift.framework.dSYM; sourceTree = "<group>"; };
E4B11B60226A4BFF0075461B /* PlayerReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerReducer.swift; sourceTree = "<group>"; };
E4B11B62226A4C510075461B /* AppReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReducer.swift; sourceTree = "<group>"; };
E4B11B65226A4F830075461B /* PlayerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerState.swift; sourceTree = "<group>"; };
E4B11B67226A4FA00075461B /* QueueState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueState.swift; sourceTree = "<group>"; };
E4B11B69226A4FBC0075461B /* AlbumListState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumListState.swift; sourceTree = "<group>"; };
E4B11B72226A6C770075461B /* TrackTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackTimer.swift; sourceTree = "<group>"; };
E4B11B74226CC4D30075461B /* QueueReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueReducer.swift; sourceTree = "<group>"; };
E4B11B78226D346B0075461B /* AlbumListReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumListReducer.swift; sourceTree = "<group>"; };
E4B11B7C2274E36D0075461B /* Differ.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = Differ.framework.dSYM; path = Carthage/Build/Mac/Differ.framework.dSYM; sourceTree = "<group>"; };
E4B11B7D2274E36D0075461B /* Differ.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Differ.framework; path = Carthage/Build/Mac/Differ.framework; sourceTree = "<group>"; };
E4B11BA82274EDE30075461B /* Loading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Loading.swift; sourceTree = "<group>"; };
E4B11BAF2274F71A0075461B /* EnumEquatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnumEquatable.swift; sourceTree = "<group>"; };
E4B11BB52275374B0075461B /* UserNotificationsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserNotificationsController.swift; sourceTree = "<group>"; };
E4B11BB7227538FA0075461B /* CurrentCoverArtView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentCoverArtView.swift; sourceTree = "<group>"; };
E4B11BBD2275EDAA0075461B /* PlayerActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerActions.swift; sourceTree = "<group>"; };
E4B11BBF2275EE150075461B /* QueueActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueActions.swift; sourceTree = "<group>"; };
E4B11BC12275EE410075461B /* AlbumListActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumListActions.swift; sourceTree = "<group>"; };
E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; }; E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesWindowController.swift; sourceTree = "<group>"; };
E4C8B53D22349002009A20F3 /* MPDIdle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDIdle.swift; sourceTree = "<group>"; }; E4C8B53D22349002009A20F3 /* MPDIdle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDIdle.swift; sourceTree = "<group>"; };
E4E8CC8F2204EC7F0024217A /* Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Delegate.swift; sourceTree = "<group>"; }; E4E8CC8F2204EC7F0024217A /* Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Delegate.swift; sourceTree = "<group>"; };
@ -253,6 +300,10 @@
E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueDataSource.swift; sourceTree = "<group>"; }; E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueDataSource.swift; sourceTree = "<group>"; };
E4F6B462221E125900ACF42A /* QueueItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueItem.swift; sourceTree = "<group>"; }; E4F6B462221E125900ACF42A /* QueueItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueItem.swift; sourceTree = "<group>"; };
E4F6B466221E233200ACF42A /* AlbumDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDataSource.swift; sourceTree = "<group>"; }; E4F6B466221E233200ACF42A /* AlbumDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDataSource.swift; sourceTree = "<group>"; };
E4FF718D2276010E00D4C412 /* PreferencesState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesState.swift; sourceTree = "<group>"; };
E4FF718F227601B400D4C412 /* PreferencesReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesReducer.swift; sourceTree = "<group>"; };
E4FF71912276029000D4C412 /* PreferencesActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesActions.swift; sourceTree = "<group>"; };
E4FF71932276043A00D4C412 /* MPDServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDServer.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@ -260,13 +311,14 @@
isa = PBXFrameworksBuildPhase; isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
E45E4FE122516953004B537F /* CryptoSwift.framework in Frameworks */, E4B11BA72274E4500075461B /* libmpdclient.2.dylib in Frameworks */,
E41B22C021FB6BBA00D544F6 /* libmpdclient.2.dylib in Frameworks */, E4B11B9A2274E43B0075461B /* CryptoSwift.framework in Frameworks */,
E421ACA3221F73C4008B2449 /* MediaKeyTap.framework in Frameworks */, E4B11B962274E43B0075461B /* Differ.framework in Frameworks */,
E450AD8622262AE60091BED3 /* SwiftyJSON.framework in Frameworks */, E4B11BA02274E43B0075461B /* SwiftyJSON.framework in Frameworks */,
E45E4FE0225168DA004B537F /* CryptoSwift.framework in Frameworks */, E4B11B9E2274E43B0075461B /* PromiseKit.framework in Frameworks */,
E450ADA32229E7E00091BED3 /* PMKFoundation.framework in Frameworks */, E4B11B9C2274E43B0075461B /* PMKFoundation.framework in Frameworks */,
E450AD9222262C970091BED3 /* PromiseKit.framework in Frameworks */, E4B11BA22274E43B0075461B /* MediaKeyTap.framework in Frameworks */,
E4B11B982274E43B0075461B /* ReSwift.framework in Frameworks */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@ -316,6 +368,7 @@
E407861A2110CE6E006887B1 /* Persephone */ = { E407861A2110CE6E006887B1 /* Persephone */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E4B11BAE2274F7030075461B /* Protocols */,
E407861B2110CE6E006887B1 /* AppDelegate.swift */, E407861B2110CE6E006887B1 /* AppDelegate.swift */,
E407861F2110CE70006887B1 /* Assets.xcassets */, E407861F2110CE70006887B1 /* Assets.xcassets */,
E4D1B597220BA3A20026F233 /* Controllers */, E4D1B597220BA3A20026F233 /* Controllers */,
@ -332,6 +385,7 @@
E4A83BEC2221F5DD0098FED6 /* Preferences */, E4A83BEC2221F5DD0098FED6 /* Preferences */,
E4D1B598220BA3C90026F233 /* Resources */, E4D1B598220BA3C90026F233 /* Resources */,
E4A83BF2222207BE0098FED6 /* Services */, E4A83BF2222207BE0098FED6 /* Services */,
E4B11B64226A4F460075461B /* State */,
E408D3C3220E138B0006D9BE /* Views */, E408D3C3220E138B0006D9BE /* Views */,
); );
path = Persephone; path = Persephone;
@ -390,6 +444,7 @@
children = ( children = (
E47E2FD62220720300F747E6 /* AlbumItemView.swift */, E47E2FD62220720300F747E6 /* AlbumItemView.swift */,
E47E2FD222205D2500F747E6 /* MainWindow.swift */, E47E2FD222205D2500F747E6 /* MainWindow.swift */,
E4B11BB7227538FA0075461B /* CurrentCoverArtView.swift */,
); );
path = Views; path = Views;
sourceTree = "<group>"; sourceTree = "<group>";
@ -397,6 +452,10 @@
E41B22BE21FB6B3300D544F6 /* Frameworks */ = { E41B22BE21FB6B3300D544F6 /* Frameworks */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E4B11B7D2274E36D0075461B /* Differ.framework */,
E4B11B7C2274E36D0075461B /* Differ.framework.dSYM */,
E4B11B572269296C0075461B /* ReSwift.framework */,
E4B11B582269296C0075461B /* ReSwift.framework.dSYM */,
E45E4FDE225168DA004B537F /* CryptoSwift.framework */, E45E4FDE225168DA004B537F /* CryptoSwift.framework */,
E45E4FDD225168DA004B537F /* CryptoSwift.framework.dSYM */, E45E4FDD225168DA004B537F /* CryptoSwift.framework.dSYM */,
E450ADA02229E7C90091BED3 /* PMKFoundation.framework */, E450ADA02229E7C90091BED3 /* PMKFoundation.framework */,
@ -468,9 +527,9 @@
E41E530C223EF4BA00173814 /* Extensions */ = { E41E530C223EF4BA00173814 /* Extensions */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E41E530D223EF4CF00173814 /* AlbumArtService+Caching.swift */, E41E530D223EF4CF00173814 /* CoverArtService+Caching.swift */,
E41E5311223EF74A00173814 /* AlbumArtService+Filesystem.swift */, E41E5311223EF74A00173814 /* CoverArtService+Filesystem.swift */,
E41E530F223EF6CE00173814 /* AlbumArtService+Remote.swift */, E41E530F223EF6CE00173814 /* CoverArtService+Remote.swift */,
); );
path = Extensions; path = Extensions;
sourceTree = "<group>"; sourceTree = "<group>";
@ -478,7 +537,7 @@
E450AD8922262B420091BED3 /* Operations */ = { E450AD8922262B420091BED3 /* Operations */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E450AD9422262DF10091BED3 /* AlbumArtQueue.swift */, E450AD9422262DF10091BED3 /* CoverArtQueue.swift */,
); );
path = Operations; path = Operations;
sourceTree = "<group>"; sourceTree = "<group>";
@ -513,7 +572,7 @@
E4A83BED2221F5E60098FED6 /* Controllers */ = { E4A83BED2221F5E60098FED6 /* Controllers */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E4A83BEE2221F8CF0098FED6 /* AlbumArtPrefsController.swift */, E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */,
E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */, E41EA46B221636AF0068EF46 /* GeneralPrefsViewController.swift */,
E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */, E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */,
E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */, E4C8B53B22342005009A20F3 /* PreferencesWindowController.swift */,
@ -524,13 +583,58 @@
E4A83BF2222207BE0098FED6 /* Services */ = { E4A83BF2222207BE0098FED6 /* Services */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E4A83BF3222207D50098FED6 /* AlbumArtService.swift */, E4A83BF3222207D50098FED6 /* CoverArtService.swift */,
E439109722640213002982E9 /* SongNotifierService.swift */, E439109722640213002982E9 /* SongNotifierService.swift */,
E41E530C223EF4BA00173814 /* Extensions */, E41E530C223EF4BA00173814 /* Extensions */,
); );
path = Services; path = Services;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
E4B11B5F226A4BED0075461B /* Reducers */ = {
isa = PBXGroup;
children = (
E4B11B60226A4BFF0075461B /* PlayerReducer.swift */,
E4B11B62226A4C510075461B /* AppReducer.swift */,
E4B11B74226CC4D30075461B /* QueueReducer.swift */,
E4B11B78226D346B0075461B /* AlbumListReducer.swift */,
E4FF718F227601B400D4C412 /* PreferencesReducer.swift */,
);
path = Reducers;
sourceTree = "<group>";
};
E4B11B64226A4F460075461B /* State */ = {
isa = PBXGroup;
children = (
E4B11B6B226A5AF50075461B /* Actions */,
E4B11B5F226A4BED0075461B /* Reducers */,
E4B11B52226928F20075461B /* AppState.swift */,
E4B11B65226A4F830075461B /* PlayerState.swift */,
E4B11B67226A4FA00075461B /* QueueState.swift */,
E4B11B69226A4FBC0075461B /* AlbumListState.swift */,
E4FF718D2276010E00D4C412 /* PreferencesState.swift */,
);
path = State;
sourceTree = "<group>";
};
E4B11B6B226A5AF50075461B /* Actions */ = {
isa = PBXGroup;
children = (
E4B11BC12275EE410075461B /* AlbumListActions.swift */,
E4B11BBD2275EDAA0075461B /* PlayerActions.swift */,
E4B11BBF2275EE150075461B /* QueueActions.swift */,
E4FF71912276029000D4C412 /* PreferencesActions.swift */,
);
path = Actions;
sourceTree = "<group>";
};
E4B11BAE2274F7030075461B /* Protocols */ = {
isa = PBXGroup;
children = (
E4B11BAF2274F71A0075461B /* EnumEquatable.swift */,
);
path = Protocols;
sourceTree = "<group>";
};
E4D1B594220BA2490026F233 /* Models */ = { E4D1B594220BA2490026F233 /* Models */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -558,8 +662,10 @@
E408D3C1220E134F0006D9BE /* AlbumViewController.swift */, E408D3C1220E134F0006D9BE /* AlbumViewController.swift */,
E47E2FD4222071FD00F747E6 /* AlbumViewItem.swift */, E47E2FD4222071FD00F747E6 /* AlbumViewItem.swift */,
E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */, E47E2FD022205C4600F747E6 /* MainSplitViewController.swift */,
E4405191227644340090CD6F /* MPDServerController.swift */,
E4E8CC932206097F0024217A /* NotificationsController.swift */, E4E8CC932206097F0024217A /* NotificationsController.swift */,
E4E8CC912204F4B80024217A /* QueueViewController.swift */, E4E8CC912204F4B80024217A /* QueueViewController.swift */,
E4B11BB52275374B0075461B /* UserNotificationsController.swift */,
E465049921E94DF500A70F4C /* WindowController.swift */, E465049921E94DF500A70F4C /* WindowController.swift */,
); );
path = Controllers; path = Controllers;
@ -587,10 +693,12 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
E450AD7D222620A10091BED3 /* Album.swift */, E450AD7D222620A10091BED3 /* Album.swift */,
E40F41F2221EDE27004B6CB8 /* Preferences.swift */, E4B11BA82274EDE30075461B /* Loading.swift */,
E4FF71932276043A00D4C412 /* MPDServer.swift */,
E4F6B462221E125900ACF42A /* QueueItem.swift */, E4F6B462221E125900ACF42A /* QueueItem.swift */,
E419E2862249B96600216A8C /* Song.swift */, E419E2862249B96600216A8C /* Song.swift */,
E47E2FDC2220A6D100F747E6 /* Time.swift */, E47E2FDC2220A6D100F747E6 /* Time.swift */,
E4B11B72226A6C770075461B /* TrackTimer.swift */,
); );
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
@ -606,8 +714,8 @@
E40786152110CE6E006887B1 /* Frameworks */, E40786152110CE6E006887B1 /* Frameworks */,
E40786162110CE6E006887B1 /* Resources */, E40786162110CE6E006887B1 /* Resources */,
E42A98F122430936004D8180 /* ShellScript */, E42A98F122430936004D8180 /* ShellScript */,
E41B22C221FB6C3300D544F6 /* Embed Libraries */,
E421AC9B221F7319008B2449 /* CopyFiles */, E421AC9B221F7319008B2449 /* CopyFiles */,
E4B11BA52274E43B0075461B /* Embed Frameworks */,
); );
buildRules = ( buildRules = (
); );
@ -724,6 +832,8 @@
E42A8F3C22176D6400A13ED9 /* README.md in Resources */, E42A8F3C22176D6400A13ED9 /* README.md in Resources */,
E450AD8F22262C620091BED3 /* PromiseKit.framework.dSYM in Resources */, E450AD8F22262C620091BED3 /* PromiseKit.framework.dSYM in Resources */,
E408D3CB220E341D0006D9BE /* AlbumViewItem.xib in Resources */, E408D3CB220E341D0006D9BE /* AlbumViewItem.xib in Resources */,
E4B11B5A2269296C0075461B /* ReSwift.framework.dSYM in Resources */,
E4B11B7E2274E36D0075461B /* Differ.framework.dSYM in Resources */,
E40786232110CE70006887B1 /* Main.storyboard in Resources */, E40786232110CE70006887B1 /* Main.storyboard in Resources */,
E450ADA12229E7C90091BED3 /* PMKFoundation.framework.dSYM in Resources */, E450ADA12229E7C90091BED3 /* PMKFoundation.framework.dSYM in Resources */,
E47E2FCC2220573500F747E6 /* MediaKeyTap.framework.dSYM in Resources */, E47E2FCC2220573500F747E6 /* MediaKeyTap.framework.dSYM in Resources */,
@ -771,21 +881,27 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
E4A83BEF2221F8CF0098FED6 /* AlbumArtPrefsController.swift in Sources */, E4A83BEF2221F8CF0098FED6 /* CoverArtPrefsController.swift in Sources */,
E4F6B467221E233200ACF42A /* AlbumDataSource.swift in Sources */, E4F6B467221E233200ACF42A /* AlbumDataSource.swift in Sources */,
E4B11BA92274EDE30075461B /* Loading.swift in Sources */,
E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */, E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */,
E40FE71B221B904300A4223F /* NSEvent.swift in Sources */, E40FE71B221B904300A4223F /* NSEvent.swift in Sources */,
E4B11BB62275374B0075461B /* UserNotificationsController.swift in Sources */,
E4B11B68226A4FA00075461B /* QueueState.swift in Sources */,
E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */, E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */,
E4405192227644340090CD6F /* MPDServerController.swift in Sources */,
E4C8B53E22349002009A20F3 /* MPDIdle.swift in Sources */, E4C8B53E22349002009A20F3 /* MPDIdle.swift in Sources */,
E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */, E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */,
E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */, E4C8B53C22342005009A20F3 /* PreferencesWindowController.swift in Sources */,
E4B11B63226A4C510075461B /* AppReducer.swift in Sources */,
E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */, E41E5307223C019100173814 /* MPDClient+Status.swift in Sources */,
E41E5310223EF6CE00173814 /* AlbumArtService+Remote.swift in Sources */, E41E5310223EF6CE00173814 /* CoverArtService+Remote.swift in Sources */,
E41E530B223C033700173814 /* MPDClient+Album.swift in Sources */, E41E530B223C033700173814 /* MPDClient+Album.swift in Sources */,
E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */, E42410B62241B956005ED6DF /* MPDClient+Database.swift in Sources */,
E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */, E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */,
E4B11BC02275EE150075461B /* QueueActions.swift in Sources */,
E47E2FD72220720300F747E6 /* AlbumItemView.swift in Sources */, E47E2FD72220720300F747E6 /* AlbumItemView.swift in Sources */,
E450AD9522262DF10091BED3 /* AlbumArtQueue.swift in Sources */, E450AD9522262DF10091BED3 /* CoverArtQueue.swift in Sources */,
E41E52FD223BF87300173814 /* MPDClient+Connection.swift in Sources */, E41E52FD223BF87300173814 /* MPDClient+Connection.swift in Sources */,
E450AD7E222620A10091BED3 /* Album.swift in Sources */, E450AD7E222620A10091BED3 /* Album.swift in Sources */,
E4E8CC942206097F0024217A /* NotificationsController.swift in Sources */, E4E8CC942206097F0024217A /* NotificationsController.swift in Sources */,
@ -793,33 +909,47 @@
E45962C62241A78500FC1A1E /* MPDCommand.swift in Sources */, E45962C62241A78500FC1A1E /* MPDCommand.swift in Sources */,
E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */, E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */,
E4EB2379220F10B8008C70C0 /* MPDPair.swift in Sources */, E4EB2379220F10B8008C70C0 /* MPDPair.swift in Sources */,
E4FF7190227601B400D4C412 /* PreferencesReducer.swift in Sources */,
E4F6B463221E125900ACF42A /* QueueItem.swift in Sources */, E4F6B463221E125900ACF42A /* QueueItem.swift in Sources */,
E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */, E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */,
E4B11BC22275EE410075461B /* AlbumListActions.swift in Sources */,
E4B11B61226A4C000075461B /* PlayerReducer.swift in Sources */,
E4FF71922276029000D4C412 /* PreferencesActions.swift in Sources */,
E465049A21E94DF500A70F4C /* WindowController.swift in Sources */, E465049A21E94DF500A70F4C /* WindowController.swift in Sources */,
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */, E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */,
E40F41F3221EDE27004B6CB8 /* Preferences.swift in Sources */,
E47E2FDD2220A6D100F747E6 /* Time.swift in Sources */, E47E2FDD2220A6D100F747E6 /* Time.swift in Sources */,
E419E2872249B96600216A8C /* Song.swift in Sources */, E419E2872249B96600216A8C /* Song.swift in Sources */,
E4FF718E2276010E00D4C412 /* PreferencesState.swift in Sources */,
E439109822640213002982E9 /* SongNotifierService.swift in Sources */, E439109822640213002982E9 /* SongNotifierService.swift in Sources */,
E407861C2110CE6E006887B1 /* AppDelegate.swift in Sources */, E407861C2110CE6E006887B1 /* AppDelegate.swift in Sources */,
E4B11B75226CC4D30075461B /* QueueReducer.swift in Sources */,
E41E5309223C020400173814 /* MPDClient+Command.swift in Sources */, E41E5309223C020400173814 /* MPDClient+Command.swift in Sources */,
E4B11BB02274F71A0075461B /* EnumEquatable.swift in Sources */,
E4B11B73226A6C770075461B /* TrackTimer.swift in Sources */,
E4B11B79226D346B0075461B /* AlbumListReducer.swift in Sources */,
E47E2FE52220AA0700F747E6 /* AlbumViewLayout.swift in Sources */, E47E2FE52220AA0700F747E6 /* AlbumViewLayout.swift in Sources */,
E41E52FF223BF95E00173814 /* MPDClient+Transport.swift in Sources */, E41E52FF223BF95E00173814 /* MPDClient+Transport.swift in Sources */,
E47E2FD322205D2500F747E6 /* MainWindow.swift in Sources */, E47E2FD322205D2500F747E6 /* MainWindow.swift in Sources */,
E47E2FD122205C4600F747E6 /* MainSplitViewController.swift in Sources */, E47E2FD122205C4600F747E6 /* MainSplitViewController.swift in Sources */,
E4B11BB8227538FA0075461B /* CurrentCoverArtView.swift in Sources */,
E4E8CC9A22075D370024217A /* MPDSong.swift in Sources */, E4E8CC9A22075D370024217A /* MPDSong.swift in Sources */,
E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */, E41EA46C221636AF0068EF46 /* GeneralPrefsViewController.swift in Sources */,
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */, E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */,
E4A83BF4222207D50098FED6 /* AlbumArtService.swift in Sources */, E4A83BF4222207D50098FED6 /* CoverArtService.swift in Sources */,
E47E2FD5222071FD00F747E6 /* AlbumViewItem.swift in Sources */, E47E2FD5222071FD00F747E6 /* AlbumViewItem.swift in Sources */,
E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */, E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */,
E41E530E223EF4CF00173814 /* AlbumArtService+Caching.swift in Sources */, E4B11B66226A4F830075461B /* PlayerState.swift in Sources */,
E4B11BBE2275EDAA0075461B /* PlayerActions.swift in Sources */,
E4FF71942276043A00D4C412 /* MPDServer.swift in Sources */,
E41E530E223EF4CF00173814 /* CoverArtService+Caching.swift in Sources */,
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */, E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */,
E41E5312223EF74A00173814 /* AlbumArtService+Filesystem.swift in Sources */, E41E5312223EF74A00173814 /* CoverArtService+Filesystem.swift in Sources */,
E41E5301223BF99300173814 /* MPDClient+Queue.swift in Sources */, E41E5301223BF99300173814 /* MPDClient+Queue.swift in Sources */,
E4EB237B220F7CF1008C70C0 /* MPDAlbum.swift in Sources */, E4EB237B220F7CF1008C70C0 /* MPDAlbum.swift in Sources */,
E41E5303223BF9C300173814 /* MPDClient+Idle.swift in Sources */, E41E5303223BF9C300173814 /* MPDClient+Idle.swift in Sources */,
E4B11B53226928F20075461B /* AppState.swift in Sources */,
E435E3E4221CD75D00184CFC /* NSImage.swift in Sources */, E435E3E4221CD75D00184CFC /* NSImage.swift in Sources */,
E4B11B6A226A4FBC0075461B /* AlbumListState.swift in Sources */,
E41E5305223BFB0700173814 /* MPDClient+Error.swift in Sources */, E41E5305223BFB0700173814 /* MPDClient+Error.swift in Sources */,
E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */, E435E3E2221CD4E200184CFC /* NSFont.swift in Sources */,
); );

View File

@ -6,52 +6,90 @@
// Copyright © 2018 Dan Barber. All rights reserved. // Copyright © 2018 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
import ReSwift
import MediaKeyTap import MediaKeyTap
@NSApplicationMain @NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, MediaKeyTapDelegate { class AppDelegate: NSObject,
var preferences = Preferences() NSApplicationDelegate,
MediaKeyTapDelegate {
var mediaKeyTap: MediaKeyTap? var mediaKeyTap: MediaKeyTap?
var userNotificationsController = UserNotificationsController()
var mpdServerController = MPDServerController()
static let mpdClient = MPDClient( static let mpdClient = MPDClient(
withDelegate: NotificationsController() withDelegate: NotificationsController()
) )
func applicationDidFinishLaunching(_ aNotification: Notification) { static let trackTimer = TrackTimer()
connect()
preferences.addObserver(self, forKeyPath: "mpdHost") static let store = Store<AppState>(reducer: appReducer, state: nil)
preferences.addObserver(self, forKeyPath: "mpdPort")
func applicationDidFinishLaunching(_ aNotification: Notification) {
mpdServerController.connect()
mediaKeyTap = MediaKeyTap(delegate: self) mediaKeyTap = MediaKeyTap(delegate: self)
mediaKeyTap?.start() mediaKeyTap?.start()
NotificationCenter.default.addObserver( AppDelegate.store.subscribe(self) {
self, $0.select { $0.playerState }
selector: #selector(enableUpdateDatabaseMenuItem), }
name: Notification.databaseUpdateFinished,
object: AppDelegate.mpdClient
)
} }
func applicationWillTerminate(_ aNotification: Notification) { func applicationWillTerminate(_ aNotification: Notification) {
disconnect() mpdServerController.disconnect()
} }
override func observeValue( func applicationDockMenu(_ sender: NSApplication) -> NSMenu? {
forKeyPath keyPath: String?, let dockMenu = NSMenu()
of object: Any?, dockMenu.autoenablesItems = false
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer? guard let state = AppDelegate.store.state.playerState.state else { return nil }
) {
switch keyPath { if let currentSong = AppDelegate.store.state.playerState.currentSong,
case "mpdHost", "mpdPort": state.isOneOf([.playing, .paused]) {
disconnect()
connect() let nowPlayingItem = NSMenuItem(title: "Now Playing", action: nil, keyEquivalent: "")
default: let songItem = NSMenuItem(title: currentSong.title, action: nil, keyEquivalent: "")
break let albumItem = NSMenuItem(
title: "\(currentSong.artist)\(currentSong.album.title)",
action: nil,
keyEquivalent: ""
)
nowPlayingItem.isEnabled = false
songItem.indentationLevel = 1
songItem.isEnabled = false
albumItem.indentationLevel = 1
albumItem.isEnabled = false
dockMenu.addItem(nowPlayingItem)
dockMenu.addItem(songItem)
dockMenu.addItem(albumItem)
dockMenu.addItem(NSMenuItem.separator())
} }
let playPauseMenuItem = NSMenuItem(
title: state == .playing ? "Pause" : "Play",
action: #selector(playPauseMenuAction),
keyEquivalent: ""
)
let stopMenuItem = NSMenuItem(title: "Stop", action: #selector(stopMenuAction), keyEquivalent: "")
let nextTrackMenuItem = NSMenuItem(title: "Next", action: #selector(nextTrackMenuAction), keyEquivalent: "")
let prevTrackMenuItem = NSMenuItem(title: "Previous", action: #selector(prevTrackMenuAction), keyEquivalent: "")
playPauseMenuItem.isEnabled = state.isOneOf([.playing, .paused, .stopped])
stopMenuItem.isEnabled = state.isOneOf([.playing, .paused])
nextTrackMenuItem.isEnabled = state.isOneOf([.playing, .paused])
prevTrackMenuItem.isEnabled = state.isOneOf([.playing, .paused])
dockMenu.addItem(playPauseMenuItem)
dockMenu.addItem(stopMenuItem)
dockMenu.addItem(nextTrackMenuItem)
dockMenu.addItem(prevTrackMenuItem)
return dockMenu
} }
func handle(mediaKey: MediaKey, event: KeyEvent) { func handle(mediaKey: MediaKey, event: KeyEvent) {
@ -65,25 +103,50 @@ class AppDelegate: NSObject, NSApplicationDelegate, MediaKeyTapDelegate {
} }
} }
func connect() { // func setDockTransportControlState(_ state: MPDClient.MPDStatus.State) {
AppDelegate.mpdClient.connect( // playPauseMenuItem.isEnabled = state.isOneOf([.playing, .paused, .stopped])
host: preferences.mpdHostOrDefault, // stopMenuItem.isEnabled = state.isOneOf([.playing, .paused])
port: preferences.mpdPortOrDefault // nextTrackMenuItem.isEnabled = state.isOneOf([.playing, .paused])
) // prevTrackMenuItem.isEnabled = state.isOneOf([.playing, .paused])
} //
// if state.isOneOf([.paused, .stopped, .unknown]) {
func disconnect() { // playPauseMenuItem.title = "Play"
AppDelegate.mpdClient.disconnect() // } else {
} // playPauseMenuItem.title = "Pause"
// }
@IBAction func updateDatabase(_ sender: NSMenuItem) { // }
sender.isEnabled = false
AppDelegate.mpdClient.updateDatabase()
}
@objc func enableUpdateDatabaseMenuItem() { @objc func enableUpdateDatabaseMenuItem() {
updateDatabaseMenuItem?.isEnabled = true updateDatabaseMenuItem?.isEnabled = true
} }
@IBAction func updateDatabase(_ sender: NSMenuItem) {
AppDelegate.mpdClient.updateDatabase()
}
@IBAction func playPauseMenuAction(_ sender: NSMenuItem) {
AppDelegate.mpdClient.playPause()
}
@IBAction func stopMenuAction(_ sender: NSMenuItem) {
AppDelegate.mpdClient.stop()
}
@IBAction func nextTrackMenuAction(_ sender: NSMenuItem) {
AppDelegate.mpdClient.nextTrack()
}
@IBAction func prevTrackMenuAction(_ sender: Any) {
AppDelegate.mpdClient.prevTrack()
}
@IBOutlet weak var updateDatabaseMenuItem: NSMenuItem! @IBOutlet weak var updateDatabaseMenuItem: NSMenuItem!
} }
extension AppDelegate: StoreSubscriber {
typealias StoreSubscriberStateType = PlayerState
func newState(state: PlayerState) {
updateDatabaseMenuItem.isEnabled = !state.databaseUpdating
guard let state = state.state else { return }
// setDockTransportControlState(state)
}
}

View File

@ -6,13 +6,13 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
import ReSwift
import Differ
class AlbumViewController: NSViewController, class AlbumViewController: NSViewController,
NSCollectionViewDelegate, NSCollectionViewDelegate,
NSCollectionViewDelegateFlowLayout { NSCollectionViewDelegateFlowLayout {
var preferences = Preferences()
let paddingWidth: CGFloat = 40 let paddingWidth: CGFloat = 40
let gutterWidth: CGFloat = 20 let gutterWidth: CGFloat = 20
@ -21,26 +21,13 @@ class AlbumViewController: NSViewController,
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
AppDelegate.store.subscribe(self) {
$0.select { $0.albumListState }
}
albumScrollView.postsBoundsChangedNotifications = true albumScrollView.postsBoundsChangedNotifications = true
NotificationCenter.default.addObserver(
self,
selector: #selector(updateAlbums(_:)),
name: Notification.loadedAlbums,
object: AppDelegate.mpdClient
)
NotificationCenter.default.addObserver(
self,
selector: #selector(clearAlbums(_:)),
name: Notification.willDisconnect,
object: AppDelegate.mpdClient
)
albumCollectionView.dataSource = dataSource albumCollectionView.dataSource = dataSource
preferences.addObserver(self, forKeyPath: "mpdLibraryDir")
preferences.addObserver(self, forKeyPath: "fetchMissingArtworkFromInternet")
} }
override func viewWillLayout() { override func viewWillLayout() {
@ -72,27 +59,25 @@ class AlbumViewController: NSViewController,
case "mpdLibraryDir": case "mpdLibraryDir":
albumCollectionView.reloadData() albumCollectionView.reloadData()
case "fetchMissingArtworkFromInternet": case "fetchMissingArtworkFromInternet":
dataSource.resetCoverArt() AppDelegate.store.dispatch(ResetAlbumListCoverArtAction())
albumCollectionView.reloadData()
default: default:
break break
} }
} }
@objc func updateAlbums(_ notification: Notification) {
guard let albums = notification.userInfo?[Notification.albumsKey] as? [MPDClient.MPDAlbum]
else { return }
dataSource.albums = albums.map { Album(mpdAlbum: $0) }
albumCollectionView.reloadData()
}
@objc func clearAlbums(_ notification: Notification) {
dataSource.albums = []
albumCollectionView.reloadData()
}
@IBOutlet var albumScrollView: NSScrollView! @IBOutlet var albumScrollView: NSScrollView!
@IBOutlet var albumCollectionView: NSCollectionView! @IBOutlet var albumCollectionView: NSCollectionView!
} }
extension AlbumViewController: StoreSubscriber {
typealias StoreSubscriberStateType = AlbumListState
func newState(state: StoreSubscriberStateType) {
let oldAlbums = dataSource.albums
dataSource.albums = state.albums
albumCollectionView.animateItemChanges(
oldData: oldAlbums,
newData: dataSource.albums
)
}
}

View File

@ -6,7 +6,7 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
class AlbumViewItem: NSCollectionViewItem { class AlbumViewItem: NSCollectionViewItem {
var observer: NSKeyValueObservation? var observer: NSKeyValueObservation?
@ -32,9 +32,10 @@ class AlbumViewItem: NSCollectionViewItem {
albumTitle.stringValue = album.title albumTitle.stringValue = album.title
albumArtist.stringValue = album.artist albumArtist.stringValue = album.artist
if let coverArt = album.coverArt { switch album.coverArt {
albumCoverView.image = coverArt case .loaded(let coverArt):
} else { albumCoverView.image = coverArt ?? .defaultCoverArt
default:
albumCoverView.image = .defaultCoverArt albumCoverView.image = .defaultCoverArt
} }
} }

View File

@ -0,0 +1,38 @@
//
// MPDServerController.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/28.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
import ReSwift
class MPDServerController {
init() {
AppDelegate.store.subscribe(self) {
$0.select { $0.preferencesState.mpdServer }
}
}
func connect() {
AppDelegate.mpdClient.connect(
host: AppDelegate.store.state.preferencesState.mpdServer.hostOrDefault,
port: AppDelegate.store.state.preferencesState.mpdServer.portOrDefault
)
}
func disconnect() {
AppDelegate.mpdClient.disconnect()
}
}
extension MPDServerController: StoreSubscriber {
typealias StoreSubscriberStateType = MPDServer
func newState(state: MPDServer) {
disconnect()
connect()
}
}

View File

@ -6,7 +6,7 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
class MainSplitViewController: NSSplitViewController { class MainSplitViewController: NSSplitViewController {
override func keyDown(with event: NSEvent) { override func keyDown(with event: NSEvent) {

View File

@ -1,5 +1,5 @@
// //
// MPDClientNotificationHandler.swift // NotificationsController.swift
// Persephone // Persephone
// //
// Created by Daniel Barber on 2019/2/02. // Created by Daniel Barber on 2019/2/02.
@ -16,53 +16,46 @@ class NotificationsController: MPDClientDelegate {
} }
func willDisconnect(mpdClient: MPDClient) { func willDisconnect(mpdClient: MPDClient) {
DispatchQueue.main.async {
AppDelegate.store.dispatch(UpdateAlbumListAction(albums: []))
}
sendNotification(name: Notification.willDisconnect) sendNotification(name: Notification.willDisconnect)
} }
func didUpdateState(mpdClient: MPDClient, state: MPDClient.MPDStatus.State) { func didUpdateStatus(mpdClient: MPDClient, status: MPDClient.MPDStatus) {
sendNotification( DispatchQueue.main.async {
name: Notification.stateChanged, AppDelegate.store.dispatch(UpdateStatusAction(status: status))
userInfo: [Notification.stateKey: state]
)
} }
func didUpdateTime(mpdClient: MPDClient, total: UInt, elapsedMs: UInt) {
sendNotification(
name: Notification.timeChanged,
userInfo: [
Notification.totalTimeKey: total,
Notification.elapsedTimeMsKey: elapsedMs
]
)
} }
func willStartDatabaseUpdate(mpdClient: MPDClient) { func willStartDatabaseUpdate(mpdClient: MPDClient) {
sendNotification(name: Notification.databaseUpdateStarted) DispatchQueue.main.async {
AppDelegate.store.dispatch(StartedDatabaseUpdateAction())
}
} }
func didFinishDatabaseUpdate(mpdClient: MPDClient) { func didFinishDatabaseUpdate(mpdClient: MPDClient) {
sendNotification(name: Notification.databaseUpdateFinished) DispatchQueue.main.async {
AppDelegate.store.dispatch(FinishedDatabaseUpdateAction())
}
} }
func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.MPDSong]) { func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.MPDSong]) {
sendNotification( DispatchQueue.main.async {
name: Notification.queueChanged, AppDelegate.store.dispatch(UpdateQueueAction(queue: queue))
userInfo: [Notification.queueKey: queue] }
)
} }
func didUpdateQueuePos(mpdClient: MPDClient, song: Int) { func didUpdateQueuePos(mpdClient: MPDClient, song: Int) {
sendNotification( DispatchQueue.main.async {
name: Notification.queuePosChanged, AppDelegate.store.dispatch(UpdateQueuePosAction(queuePos: song))
userInfo: [Notification.queuePosKey: song] }
)
} }
func didLoadAlbums(mpdClient: MPDClient, albums: [MPDClient.MPDAlbum]) { func didLoadAlbums(mpdClient: MPDClient, albums: [MPDClient.MPDAlbum]) {
sendNotification( DispatchQueue.main.async {
name: Notification.loadedAlbums, AppDelegate.store.dispatch(UpdateAlbumListAction(albums: albums))
userInfo: [Notification.albumsKey: albums] }
)
} }
private func sendNotification(name: Notification.Name, userInfo: [AnyHashable : Any] = [:]) { private func sendNotification(name: Notification.Name, userInfo: [AnyHashable : Any] = [:]) {

View File

@ -6,20 +6,22 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
import PromiseKit import ReSwift
class QueueViewController: NSViewController, class QueueViewController: NSViewController,
NSOutlineViewDelegate { NSOutlineViewDelegate {
var dataSource = QueueDataSource() var dataSource = QueueDataSource()
@IBOutlet var queueView: NSOutlineView! @IBOutlet var queueView: NSOutlineView!
@IBOutlet var queueAlbumArtImage: NSImageView! @IBOutlet var queueCoverArtImage: NSImageView!
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
setupNotificationObservers() AppDelegate.store.subscribe(self) {
$0.select { $0.queueState }
}
queueView.dataSource = dataSource queueView.dataSource = dataSource
queueView.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle queueView.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle
@ -42,60 +44,6 @@ class QueueViewController: NSViewController,
} }
} }
@objc func stateChanged(_ notification: Notification) {
guard let state = notification.userInfo?[Notification.stateKey] as? MPDClient.MPDStatus.State
else { return }
dataSource.setQueueIcon(state)
}
func notifyTrack() {
guard let currentSong = dataSource.currentSong,
let status = AppDelegate.mpdClient.status,
status.state == .playing
else { return }
SongNotifierService(song: currentSong, image: queueAlbumArtImage.image)
.deliver()
}
@objc func queueChanged(_ notification: Notification) {
guard let queue = notification.userInfo?[Notification.queueKey] as? [MPDClient.MPDSong]
else { return }
dataSource.updateQueue(queue)
queueView.reloadData()
}
@objc func queuePosChanged(_ notification: Notification) {
guard let queuePos = notification.userInfo?[Notification.queuePosKey] as? Int
else { return }
dataSource.setQueuePos(queuePos)
queueView.reloadData()
updateAlbumArt()
}
func updateAlbumArt() {
if let playingQueueItem = dataSource.queue.first(where: { $0.isPlaying }) {
let albumArtService = AlbumArtService(song: playingQueueItem.song)
albumArtService.fetchBigAlbumArt()
.done() {
if let image = $0 {
self.queueAlbumArtImage.image = image
} else {
self.queueAlbumArtImage.image = NSImage.defaultCoverArt
}
self.notifyTrack()
}
.cauterize()
} else {
queueAlbumArtImage.image = NSImage.defaultCoverArt
}
}
func outlineView( func outlineView(
_ outlineView: NSOutlineView, _ outlineView: NSOutlineView,
selectionIndexesForProposedSelection proposedSelectionIndexes: IndexSet selectionIndexesForProposedSelection proposedSelectionIndexes: IndexSet
@ -168,27 +116,14 @@ func cellForSongTitle(_ outlineView: NSOutlineView, with queueItem: QueueItem) -
return cellView return cellView
} }
}
func setupNotificationObservers() { extension QueueViewController: StoreSubscriber {
NotificationCenter.default.addObserver( typealias StoreSubscriberStateType = QueueState
self,
selector: #selector(stateChanged(_:)),
name: Notification.stateChanged,
object: AppDelegate.mpdClient
)
NotificationCenter.default.addObserver( func newState(state: StoreSubscriberStateType) {
self, dataSource.queue = state.queue
selector: #selector(queueChanged(_:)), dataSource.setQueueIcon(state)
name: Notification.queueChanged, queueView.reloadData()
object: AppDelegate.mpdClient
)
NotificationCenter.default.addObserver(
self,
selector: #selector(queuePosChanged(_:)),
name: Notification.queuePosChanged,
object: AppDelegate.mpdClient
)
} }
} }

View File

@ -0,0 +1,42 @@
//
// UserNotificationsController.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/27.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
import ReSwift
class UserNotificationsController {
init() {
AppDelegate.store.subscribe(self) {
$0.select { $0.playerState.currentSong }
}
}
func notifyTrack(_ state: Song?) {
guard let currentSong = state,
let status = AppDelegate.mpdClient.status,
status.state == .playing
else { return }
let coverArtService = CoverArtService(song: currentSong)
coverArtService.fetchBigCoverArt()
.done() {
SongNotifierService(song: currentSong, image: $0)
.deliver()
}
.cauterize()
}
}
extension UserNotificationsController: StoreSubscriber {
typealias StoreSubscriberStateType = Song?
func newState(state: Song?) {
notifyTrack(state)
}
}

View File

@ -6,7 +6,8 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
import ReSwift
class WindowController: NSWindowController { class WindowController: NSWindowController {
enum TransportAction: Int { enum TransportAction: Int {
@ -14,41 +15,16 @@ class WindowController: NSWindowController {
} }
var state: MPDClient.MPDStatus.State? var state: MPDClient.MPDStatus.State?
var totalTime: UInt?
var elapsedTimeMs: UInt?
var trackTimer: Timer? var trackTimer: Timer?
override func windowDidLoad() { override func windowDidLoad() {
super.windowDidLoad() super.windowDidLoad()
window?.titleVisibility = .hidden window?.titleVisibility = .hidden
window?.isExcludedFromWindowsMenu = true
NotificationCenter.default.addObserver( AppDelegate.store.subscribe(self) {
self, $0.select { $0.playerState }
selector: #selector(stateChanged(_:)), }
name: Notification.stateChanged,
object: AppDelegate.mpdClient
)
NotificationCenter.default.addObserver(
self,
selector: #selector(timeChanged(_:)),
name: Notification.timeChanged,
object: AppDelegate.mpdClient
)
NotificationCenter.default.addObserver(
self,
selector: #selector(startDatabaseUpdatingIndicator),
name: Notification.databaseUpdateStarted,
object: AppDelegate.mpdClient
)
NotificationCenter.default.addObserver(
self,
selector: #selector(stopDatabaseUpdatingIndicator),
name: Notification.databaseUpdateFinished,
object: AppDelegate.mpdClient
)
trackProgress.font = .timerFont trackProgress.font = .timerFont
trackRemaining.font = .timerFont trackRemaining.font = .timerFont
@ -63,17 +39,8 @@ class WindowController: NSWindowController {
} }
} }
@objc func stateChanged(_ notification: Notification) { func setTransportControlState(_ state: PlayerState) {
guard let state = notification.userInfo?[Notification.stateKey] as? MPDClient.MPDStatus.State guard let state = state.state else { return }
else { return }
self.state = state
setTransportControlState()
}
func setTransportControlState() {
guard let state = state else { return }
transportControls.setEnabled(state.isOneOf([.playing, .paused]), forSegment: 0) transportControls.setEnabled(state.isOneOf([.playing, .paused]), forSegment: 0)
transportControls.setEnabled(state.isOneOf([.playing, .paused, .stopped]), forSegment: 1) transportControls.setEnabled(state.isOneOf([.playing, .paused, .stopped]), forSegment: 1)
@ -87,74 +54,37 @@ class WindowController: NSWindowController {
} }
} }
@objc func timeChanged(_ notification: Notification) { func setTrackProgressControls(_ playerState: PlayerState) {
guard let totalTime = notification.userInfo?[Notification.totalTimeKey] as? UInt, guard let state = playerState.state,
let elapsedTimeMs = notification.userInfo?[Notification.elapsedTimeMsKey] as? UInt let totalTime = playerState.totalTime,
let elapsedTimeMs = playerState.elapsedTimeMs
else { return } else { return }
self.totalTime = totalTime trackProgressBar.isEnabled = state.isOneOf([.playing, .paused])
self.elapsedTimeMs = elapsedTimeMs
setTrackProgressControls()
}
func setTrackProgressControls() {
guard let totalTime = totalTime,
let elapsedTimeMs = elapsedTimeMs
else { return }
trackProgressBar.isEnabled = [.playing, .paused].contains(state)
trackProgressBar.maxValue = Double(totalTime * 1000) trackProgressBar.maxValue = Double(totalTime * 1000)
if state == .playing {
trackTimer?.invalidate()
trackTimer = Timer.scheduledTimer(
timeInterval: 0.25,
target: self,
selector: #selector(updateProgress(_:)),
userInfo: [
"startTime": CACurrentMediaTime(),
"startElapsed": Double(elapsedTimeMs) / 1000
],
repeats: true
)
} else {
trackTimer?.invalidate()
trackProgressBar.integerValue = Int(elapsedTimeMs) trackProgressBar.integerValue = Int(elapsedTimeMs)
setTimeElapsed()
setTimeRemaining() setTimeElapsed(elapsedTimeMs)
setTimeRemaining(elapsedTimeMs, totalTime * 1000)
}
func setDatabaseUpdatingIndicator(_ playerState: PlayerState) {
if playerState.databaseUpdating {
startDatabaseUpdatingIndicator()
} else {
stopDatabaseUpdatingIndicator()
} }
} }
@objc func updateProgress(_ timer: Timer) { func startDatabaseUpdatingIndicator() {
let currentTime = CACurrentMediaTime()
guard let userInfo = timer.userInfo as? Dictionary<String, Any>,
let startTime = userInfo["startTime"] as? Double,
let startElapsed = userInfo["startElapsed"] as? Double
else { return }
let timeDiff = currentTime - startTime
let newElapsedTimeMs = (startElapsed + timeDiff) * 1000
self.elapsedTimeMs = UInt(newElapsedTimeMs)
trackProgressBar.integerValue = Int(newElapsedTimeMs)
setTimeElapsed()
setTimeRemaining()
}
@objc func startDatabaseUpdatingIndicator() {
databaseUpdatingIndicator.startAnimation(self) databaseUpdatingIndicator.startAnimation(self)
} }
@objc func stopDatabaseUpdatingIndicator() { func stopDatabaseUpdatingIndicator() {
databaseUpdatingIndicator.stopAnimation(self) databaseUpdatingIndicator.stopAnimation(self)
} }
func setTimeElapsed() { func setTimeElapsed(_ elapsedTimeMs: UInt?) {
guard let elapsedTimeMs = elapsedTimeMs else { return } guard let elapsedTimeMs = elapsedTimeMs else { return }
let time = Time(timeInSeconds: Int(elapsedTimeMs) / 1000) let time = Time(timeInSeconds: Int(elapsedTimeMs) / 1000)
@ -162,30 +92,30 @@ class WindowController: NSWindowController {
trackProgress.stringValue = time.formattedTime trackProgress.stringValue = time.formattedTime
} }
func setTimeRemaining() { func setTimeRemaining(_ elapsedTimeMs: UInt?, _ totalTime: UInt?) {
guard let elapsedTimeMs = elapsedTimeMs, guard let elapsedTimeMs = elapsedTimeMs,
let totalTime = totalTime let totalTime = totalTime
else { return } else { return }
let time = Time(timeInSeconds: -(Int(totalTime) - Int(elapsedTimeMs) / 1000)) let time = Time(
timeInSeconds: -(Int(totalTime) - Int(elapsedTimeMs)) / 1000
)
trackRemaining.stringValue = time.formattedTime trackRemaining.stringValue = time.formattedTime
} }
// TODO: Refactor this using a gesture recognizer // TODO: Refactor this using a gesture recognizer
@IBAction func changeTrackProgress(_ sender: NSSlider) { @IBAction func changeTrackProgress(_ sender: NSSlider) {
guard let event = NSApplication.shared.currentEvent else { guard let event = NSApplication.shared.currentEvent
return else { return }
}
switch event.type { switch event.type {
case .leftMouseDown: case .leftMouseDown:
trackTimer?.invalidate() trackTimer?.invalidate()
case .leftMouseDragged: case .leftMouseDragged:
self.elapsedTimeMs = UInt(sender.integerValue) AppDelegate.store.dispatch(
UpdateElapsedTimeAction(elapsedTimeMs: UInt(sender.integerValue))
setTimeElapsed() )
setTimeRemaining()
case .leftMouseUp: case .leftMouseUp:
let seekTime = Float(sender.integerValue) / 1000 let seekTime = Float(sender.integerValue) / 1000
@ -218,3 +148,17 @@ class WindowController: NSWindowController {
@IBOutlet var trackRemaining: NSTextField! @IBOutlet var trackRemaining: NSTextField!
@IBOutlet var databaseUpdatingIndicator: NSProgressIndicator! @IBOutlet var databaseUpdatingIndicator: NSProgressIndicator!
} }
extension WindowController: StoreSubscriber {
typealias StoreSubscriberStateType = PlayerState
func newState(state: StoreSubscriberStateType) {
self.state = state.state
DispatchQueue.main.async {
self.setTransportControlState(state)
self.setTrackProgressControls(state)
self.setDatabaseUpdatingIndicator(state)
}
}
}

View File

@ -6,7 +6,7 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
import PromiseKit import PromiseKit
class AlbumDataSource: NSObject, NSCollectionViewDataSource { class AlbumDataSource: NSObject, NSCollectionViewDataSource {
@ -16,14 +16,6 @@ class AlbumDataSource: NSObject, NSCollectionViewDataSource {
return albums.count return albums.count
} }
func resetCoverArt() {
albums = albums.map {
var album = $0
album.coverArtFetched = false
return album
}
}
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
let item = collectionView.makeItem(withIdentifier: .albumViewItem, for: indexPath) let item = collectionView.makeItem(withIdentifier: .albumViewItem, for: indexPath)
guard let albumViewItem = item as? AlbumViewItem else { return item } guard let albumViewItem = item as? AlbumViewItem else { return item }
@ -31,23 +23,23 @@ class AlbumDataSource: NSObject, NSCollectionViewDataSource {
albumViewItem.view.wantsLayer = true albumViewItem.view.wantsLayer = true
albumViewItem.setAlbum(albums[indexPath.item]) albumViewItem.setAlbum(albums[indexPath.item])
if albums[indexPath.item].coverArt == nil && switch albums[indexPath.item].coverArt {
!albums[indexPath.item].coverArtFetched { case .notLoaded:
AppDelegate.mpdClient.getAlbumFirstSong(for: albums[indexPath.item].mpdAlbum) { AppDelegate.mpdClient.getAlbumFirstSong(for: albums[indexPath.item].mpdAlbum) {
guard let song = $0 else { return } guard let song = $0 else { return }
AlbumArtService(song: Song(mpdSong: song)) CoverArtService(song: Song(mpdSong: song))
.fetchAlbumArt() .fetchCoverArt()
.done { image in .done { image in
self.albums[indexPath.item].coverArt = image
self.albums[indexPath.item].coverArtFetched = true
DispatchQueue.main.async { DispatchQueue.main.async {
collectionView.reloadItems(at: [indexPath]) AppDelegate.store.dispatch(
UpdateCoverArtAction(coverArt: image, albumIndex: indexPath.item)
)
} }
} }
} }
default:
break
} }
return albumViewItem return albumViewItem

View File

@ -6,49 +6,17 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
class QueueDataSource: NSObject, NSOutlineViewDataSource { class QueueDataSource: NSObject, NSOutlineViewDataSource {
var queue: [QueueItem] = [] var queue: [QueueItem] = []
var queuePos: Int = -1
var currentSong: Song?
var queueIcon: NSImage? = nil var queueIcon: NSImage? = nil
func updateQueue(_ queue: [MPDClient.MPDSong]) { func setQueueIcon(_ state: QueueState) {
queuePos = -1 switch state.state {
case .playing?:
self.queue = queue.enumerated().map { index, mpdSong in
let song = Song(mpdSong: mpdSong)
return QueueItem(
song: song,
queuePos: index,
isPlaying: index == queuePos
)
}
}
func setQueuePos(_ queuePos: Int) {
let oldSongRowPos = self.queuePos
let newSongRowPos = queuePos
self.queuePos = queuePos
if oldSongRowPos >= 0 {
queue[oldSongRowPos].isPlaying = false
}
if newSongRowPos >= 0 {
queue[newSongRowPos].isPlaying = true
currentSong = queue[newSongRowPos].song
} else {
currentSong = nil
}
}
func setQueueIcon(_ state: MPDClient.MPDStatus.State) {
switch state {
case .playing:
queueIcon = .playIcon queueIcon = .playIcon
case .paused: case .paused?:
queueIcon = .pauseIcon queueIcon = .pauseIcon
default: default:
queueIcon = nil queueIcon = nil

View File

@ -6,9 +6,9 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
extension CGColor { extension CGColor {
static let albumBorderColorLight = NSColor.black.withAlphaComponent(0.1).cgColor static let albumBorderColorLight = NSColor.black.withAlphaComponent(0.15).cgColor
static let albumBorderColorDark = NSColor.white.withAlphaComponent(0.1).cgColor static let albumBorderColorDark = NSColor.white.withAlphaComponent(0.15).cgColor
} }

View File

@ -6,7 +6,7 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
extension NSEvent { extension NSEvent {
static let keyCodeSpace: UInt16 = 49 static let keyCodeSpace: UInt16 = 49

View File

@ -6,7 +6,7 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
extension NSFont { extension NSFont {
static let systemFontRegular = systemFont(ofSize: 13, weight: .regular) static let systemFontRegular = systemFont(ofSize: 13, weight: .regular)

View File

@ -6,13 +6,13 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
extension NSImage { extension NSImage {
static let playIcon = NSImage(named: "playButton") static let playIcon = NSImage(named: "playButton")
static let pauseIcon = NSImage(named: "pauseButton") static let pauseIcon = NSImage(named: "pauseButton")
static let defaultCoverArt = NSImage(named: "blankAlbum") static let defaultCoverArt = NSImage(named: "defaultCoverArt")
func toFitBox(size: NSSize) -> NSImage { func toFitBox(size: NSSize) -> NSImage {
var newSize: NSSize = NSSize.zero var newSize: NSSize = NSSize.zero

View File

@ -6,7 +6,7 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
extension NSUserInterfaceItemIdentifier { extension NSUserInterfaceItemIdentifier {
static let queueSongTitleColumn = NSUserInterfaceItemIdentifier("songTitleColumn") static let queueSongTitleColumn = NSUserInterfaceItemIdentifier("songTitleColumn")

View File

@ -17,7 +17,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.11.2-alpha</string> <string>0.12.0-pre-alpha</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>LSApplicationCategoryType</key> <key>LSApplicationCategoryType</key>

View File

@ -6,7 +6,7 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
class AlbumViewLayout: NSCollectionViewFlowLayout { class AlbumViewLayout: NSCollectionViewFlowLayout {
let maxItemWidth: CGFloat = 180 let maxItemWidth: CGFloat = 180

View File

@ -29,8 +29,7 @@ extension MPDClient {
self.idle() self.idle()
self.delegate?.didConnect(mpdClient: self) self.delegate?.didConnect(mpdClient: self)
self.delegate?.didUpdateState(mpdClient: self, state: self.status!.state) self.delegate?.didUpdateStatus(mpdClient: self, status: self.status!)
self.delegate?.didUpdateTime(mpdClient: self, total: self.status!.totalTime, elapsedMs: self.status!.elapsedTimeMs)
self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue) self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue)
self.delegate?.didUpdateQueuePos(mpdClient: self, song: self.status!.song) self.delegate?.didUpdateQueuePos(mpdClient: self, song: self.status!.song)
} }

View File

@ -43,9 +43,8 @@ extension MPDClient {
self.fetchStatus() self.fetchStatus()
if let status = self.status { if let status = self.status {
self.delegate?.didUpdateStatus(mpdClient: self, status: status)
self.delegate?.didUpdateQueuePos(mpdClient: self, song: status.song) self.delegate?.didUpdateQueuePos(mpdClient: self, song: status.song)
self.delegate?.didUpdateState(mpdClient: self, state: status.state)
self.delegate?.didUpdateTime(mpdClient: self, total: status.totalTime, elapsedMs: status.elapsedTimeMs)
} }
} }
if mpdIdle.contains(.update) { if mpdIdle.contains(.update) {

View File

@ -12,8 +12,7 @@ protocol MPDClientDelegate {
func didConnect(mpdClient: MPDClient) func didConnect(mpdClient: MPDClient)
func willDisconnect(mpdClient: MPDClient) func willDisconnect(mpdClient: MPDClient)
func didUpdateState(mpdClient: MPDClient, state: MPDClient.MPDStatus.State) func didUpdateStatus(mpdClient: MPDClient, status: MPDClient.MPDStatus)
func didUpdateTime(mpdClient: MPDClient, total: UInt, elapsedMs: UInt)
func willStartDatabaseUpdate(mpdClient: MPDClient) func willStartDatabaseUpdate(mpdClient: MPDClient)
func didFinishDatabaseUpdate(mpdClient: MPDClient) func didFinishDatabaseUpdate(mpdClient: MPDClient)

View File

@ -6,13 +6,12 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
import CryptoSwift import CryptoSwift
struct Album { struct Album {
var mpdAlbum: MPDClient.MPDAlbum var mpdAlbum: MPDClient.MPDAlbum
var coverArt: NSImage? var coverArt: Loading<NSImage?> = .notLoaded
var coverArtFetched: Bool = false
init(mpdAlbum: MPDClient.MPDAlbum) { init(mpdAlbum: MPDClient.MPDAlbum) {
self.mpdAlbum = mpdAlbum self.mpdAlbum = mpdAlbum
@ -30,3 +29,11 @@ struct Album {
return "\(title) - \(artist)".sha1() return "\(title) - \(artist)".sha1()
} }
} }
extension Album: Equatable {
static func == (lhs: Album, rhs: Album) -> Bool {
return (lhs.artist == rhs.artist) &&
(lhs.title == rhs.title) &&
(lhs.coverArt ~= rhs.coverArt)
}
}

View File

@ -0,0 +1,30 @@
//
// Loading.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/27.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
enum Loading<T> {
case notLoaded
case loading
case loaded(T)
case error(Error)
}
extension Loading: EnumEquatable {
static func ~= (lhs: Loading<T>, rhs: Loading<T>) -> Bool {
switch (lhs, rhs) {
case (_, .notLoaded),
(.loading, .loading),
(.loaded, .loaded),
(.error, .error):
return true
default:
return false
}
}
}

View File

@ -0,0 +1,33 @@
//
// MPDServer.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/28.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
struct MPDServer {
let hostDefault = "127.0.0.1"
let portDefault = 6600
var host: String?
var hostOrDefault: String {
return host ?? hostDefault
}
var port: Int?
var portOrDefault: Int {
return port ?? portDefault
}
}
extension MPDServer: Equatable {
static func == (lhs: MPDServer, rhs: MPDServer) -> Bool {
return (lhs.host == rhs.host) &&
(lhs.port == rhs.port)
}
}

View File

@ -1,77 +0,0 @@
//
// Preferences.swift
// Persephone
//
// Created by Daniel Barber on 2019/2/15.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
struct Preferences {
let mpdHostDefault = "127.0.0.1"
let mpdPortDefault = 6600
let mpdLibraryDirDefault = "~/Music"
let preferences = UserDefaults.standard
var mpdHost: String? {
get {
return preferences.string(forKey: "mpdHost")
}
set {
preferences.set(newValue, forKey: "mpdHost")
}
}
var mpdHostOrDefault: String {
return mpdHost ?? mpdHostDefault
}
var mpdPort: Int? {
get {
return preferences.value(forKey: "mpdPort") as? Int
}
set {
if (newValue.map { $0 > 0 } ?? false) {
preferences.set(newValue, forKey: "mpdPort")
} else {
preferences.removeObject(forKey: "mpdPort")
}
}
}
var mpdPortOrDefault: Int {
return mpdPort ?? mpdPortDefault
}
var mpdLibraryDir: String? {
get {
return preferences.string(forKey: "mpdLibraryDir")
}
set {
preferences.set(newValue, forKey: "mpdLibraryDir")
}
}
var mpdLibraryDirOrDefault: String {
return mpdLibraryDir ?? mpdLibraryDirDefault
}
var expandedMpdLibraryDir: String {
return NSString(string: mpdLibraryDirOrDefault).expandingTildeInPath
}
var fetchMissingArtworkFromInternet: Bool {
get {
return preferences.bool(forKey: "fetchMissingArtworkFromInternet")
}
set {
preferences.set(newValue, forKey: "fetchMissingArtworkFromInternet")
}
}
func addObserver(_ observer: NSObject, forKeyPath keyPath: String) {
preferences.addObserver(observer, forKeyPath: keyPath, options: .new, context: nil)
}
}

View File

@ -13,3 +13,11 @@ struct QueueItem {
var queuePos: Int var queuePos: Int
var isPlaying: Bool var isPlaying: Bool
} }
extension QueueItem: Equatable {
static func == (lhs: QueueItem, rhs: QueueItem) -> Bool {
return (lhs.song == rhs.song) &&
(lhs.queuePos == rhs.queuePos) &&
(lhs.isPlaying == rhs.isPlaying)
}
}

View File

@ -23,3 +23,11 @@ struct Song {
return Album(mpdAlbum: mpdSong.album) return Album(mpdAlbum: mpdSong.album)
} }
} }
extension Song: Equatable {
static func == (lhs: Song, rhs: Song) -> Bool {
return (lhs.title == rhs.title) &&
(lhs.artist == rhs.artist) &&
(lhs.album == rhs.album)
}
}

View File

@ -0,0 +1,52 @@
//
// TrackTimer.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/19.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import AppKit
class TrackTimer: NSObject {
var timer: Timer?
var startTime: CFTimeInterval = CACurrentMediaTime()
var startElapsed: Double = 0
func start(elapsedTimeMs: UInt?) {
guard let elapsedTimeMs = elapsedTimeMs else { return }
startTime = CACurrentMediaTime()
startElapsed = Double(elapsedTimeMs) / 1000
DispatchQueue.main.async {
self.timer?.invalidate()
self.timer = Timer.scheduledTimer(
withTimeInterval: 0.25,
repeats: true
) { _ in
let currentTime = CACurrentMediaTime()
let timeDiff = currentTime - self.startTime
let newElapsedTimeMs = UInt((self.startElapsed + timeDiff) * 1000)
AppDelegate.store.dispatch(
UpdateElapsedTimeAction(elapsedTimeMs: newElapsedTimeMs)
)
}
}
}
func stop(elapsedTimeMs: UInt?) {
guard let elapsedTimeMs = elapsedTimeMs else { return }
DispatchQueue.main.async {
self.timer?.invalidate()
AppDelegate.store.dispatch(
UpdateElapsedTimeAction(elapsedTimeMs: elapsedTimeMs)
)
}
}
}

View File

@ -1,17 +1,17 @@
// //
// AlbumArtOperationQueue.swift // CoverArtQueue.swift
// Persephone // Persephone
// //
// Created by Daniel Barber on 2019/2/26. // Created by Daniel Barber on 2019/2/26.
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
class AlbumArtQueue { class CoverArtQueue {
static let shared = AlbumArtQueue() static let shared = CoverArtQueue()
let queue = DispatchQueue(label: "AlbumArtQueue") let queue = DispatchQueue(label: "CoverArtQueue")
var lastDispatchedTime = DispatchTime(uptimeNanoseconds: 0) - 1 var lastDispatchedTime = DispatchTime(uptimeNanoseconds: 0) - 1
func addToQueue(workItem: DispatchWorkItem) { func addToQueue(workItem: DispatchWorkItem) {

View File

@ -1,24 +1,22 @@
// //
// AlbumArtPrefsController.swift // CoverArtPrefsController.swift
// Persephone // Persephone
// //
// Created by Daniel Barber on 2019/2/23. // Created by Daniel Barber on 2019/2/23.
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
class AlbumArtPrefsController: NSViewController {
var preferences = Preferences()
class CoverArtPrefsController: NSViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
if let mpdLibraryDir = preferences.mpdLibraryDir { if let mpdLibraryDir = AppDelegate.store.state.preferencesState.mpdLibraryDir {
mpdLibraryDirField.stringValue = mpdLibraryDir mpdLibraryDirField.stringValue = mpdLibraryDir
} }
if preferences.fetchMissingArtworkFromInternet { if AppDelegate.store.state.preferencesState.fetchMissingArtworkFromInternet {
fetchMissingArtworkFromInternet.state = .on fetchMissingArtworkFromInternet.state = .on
} else { } else {
fetchMissingArtworkFromInternet.state = .off fetchMissingArtworkFromInternet.state = .off
@ -36,17 +34,17 @@ class AlbumArtPrefsController: NSViewController {
} }
@IBAction func updateMpdLibraryDir(_ sender: NSTextField) { @IBAction func updateMpdLibraryDir(_ sender: NSTextField) {
preferences.mpdLibraryDir = sender.stringValue AppDelegate.store.dispatch(UpdateMPDLibraryDir(mpdLibraryDir: sender.stringValue))
} }
@IBOutlet var mpdLibraryDirField: NSTextField! @IBOutlet var mpdLibraryDirField: NSTextField!
@IBAction func updateFetchMissingArtworkFromInternet(_ sender: NSButton) { @IBAction func updateFetchMissingArtworkFromInternet(_ sender: NSButton) {
if sender.state == .on { AppDelegate.store.dispatch(
preferences.fetchMissingArtworkFromInternet = true UpdateFetchMissingArtworkFromInternet(
} else { fetchMissingArtworkFromInternet: sender.state == .on
preferences.fetchMissingArtworkFromInternet = false )
} )
} }
@IBOutlet var fetchMissingArtworkFromInternet: NSButton! @IBOutlet var fetchMissingArtworkFromInternet: NSButton!

View File

@ -6,19 +6,19 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
import ReSwift
class GeneralPrefsViewController: NSViewController { class GeneralPrefsViewController: NSViewController {
var preferences = Preferences()
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
if let mpdHost = preferences.mpdHost { if let mpdHost = AppDelegate.store.state.preferencesState.mpdServer.host {
mpdHostField.stringValue = mpdHost mpdHostField.stringValue = mpdHost
} }
if let mpdPort = preferences.mpdPort { if let mpdPort = AppDelegate.store.state.preferencesState.mpdServer.port {
print(mpdPort)
mpdPortField.stringValue = "\(mpdPort)" mpdPortField.stringValue = "\(mpdPort)"
} }
@ -34,11 +34,11 @@ class GeneralPrefsViewController: NSViewController {
} }
@IBAction func updateMpdHost(_ sender: NSTextField) { @IBAction func updateMpdHost(_ sender: NSTextField) {
preferences.mpdHost = sender.stringValue AppDelegate.store.dispatch(UpdateServerHost(host: sender.stringValue))
} }
@IBAction func updateMpdPort(_ sender: NSTextField) { @IBAction func updateMpdPort(_ sender: NSTextField) {
preferences.mpdPort = sender.integerValue AppDelegate.store.dispatch(UpdateServerPort(port: sender.integerValue))
} }
@IBOutlet var mpdHostField: NSTextField! @IBOutlet var mpdHostField: NSTextField!

View File

@ -6,7 +6,7 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
class PreferencesViewController: NSTabViewController { class PreferencesViewController: NSTabViewController {
private lazy var tabViewSizes: [String : NSSize] = [:] private lazy var tabViewSizes: [String : NSSize] = [:]

View File

@ -6,7 +6,7 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
class PreferencesWindowController: NSWindowController, NSWindowDelegate { class PreferencesWindowController: NSWindowController, NSWindowDelegate {
override func windowDidLoad() { override func windowDidLoad() {
@ -14,6 +14,7 @@ class PreferencesWindowController: NSWindowController, NSWindowDelegate {
} }
func windowShouldClose(_ sender: NSWindow) -> Bool { func windowShouldClose(_ sender: NSWindow) -> Bool {
AppDelegate.store.dispatch(SavePreferences())
self.window?.orderOut(sender) self.window?.orderOut(sender)
return false return false
} }

View File

@ -0,0 +1,11 @@
//
// EnumEquatable.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/27.
// Copyright © 2019 Dan Barber. All rights reserved.
//
protocol EnumEquatable {
static func ~=(lhs: Self, rhs: Self) -> Bool
}

View File

@ -39,7 +39,7 @@
</textField> </textField>
<imageView identifier="albumArtwork" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="Kfb-8f-ean"> <imageView identifier="albumArtwork" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="Kfb-8f-ean">
<rect key="frame" x="0.0" y="39" width="128" height="128"/> <rect key="frame" x="0.0" y="39" width="128" height="128"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="blankAlbum" id="FsA-JX-BFh"/> <imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="defaultCoverArt" id="FsA-JX-BFh"/>
</imageView> </imageView>
<button hidden="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="n8W-do-HyG"> <button hidden="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="n8W-do-HyG">
<rect key="frame" x="43" y="81" width="42" height="43"/> <rect key="frame" x="43" y="81" width="42" height="43"/>
@ -79,7 +79,7 @@
<collectionViewItem id="Qgu-aI-55A" customClass="AlbumViewItem" customModule="Persephone" customModuleProvider="target"/> <collectionViewItem id="Qgu-aI-55A" customClass="AlbumViewItem" customModule="Persephone" customModuleProvider="target"/>
</objects> </objects>
<resources> <resources>
<image name="blankAlbum" width="128" height="128"/> <image name="defaultCoverArt" width="128" height="128"/>
<image name="playButtonLarge" width="22" height="22"/> <image name="playButtonLarge" width="22" height="22"/>
</resources> </resources>
</document> </document>

View File

@ -3,7 +3,6 @@
<dependencies> <dependencies>
<deployment identifier="macosx"/> <deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/> <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
<capability name="System colors introduced in macOS 10.14" minToolsVersion="10.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
<scenes> <scenes>
@ -91,6 +90,8 @@
<action selector="performZoom:" target="Ady-hI-5gd" id="DIl-cC-cCs"/> <action selector="performZoom:" target="Ady-hI-5gd" id="DIl-cC-cCs"/>
</connections> </connections>
</menuItem> </menuItem>
<menuItem isSeparatorItem="YES" id="QhP-iz-rRa"/>
<menuItem title="Persephone" state="on" keyEquivalent="0" id="1Sq-L7-znT"/>
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/> <menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
<menuItem title="Bring All to Front" id="LE2-aR-0XJ"> <menuItem title="Bring All to Front" id="LE2-aR-0XJ">
<modifierMask key="keyEquivalentModifierMask"/> <modifierMask key="keyEquivalentModifierMask"/>
@ -127,13 +128,13 @@
<customObject id="YLy-65-1bz" customClass="NSFontManager"/> <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/> <customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects> </objects>
<point key="canvasLocation" x="115" y="-366"/> <point key="canvasLocation" x="81" y="-332"/>
</scene> </scene>
<!--Window Controller--> <!--Window Controller-->
<scene sceneID="R2V-B0-nI4"> <scene sceneID="R2V-B0-nI4">
<objects> <objects>
<windowController id="B8D-0N-5wS" customClass="WindowController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController"> <windowController id="B8D-0N-5wS" customClass="WindowController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
<window key="window" title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="IQv-IB-iLA" customClass="MainWindow" customModule="Persephone" customModuleProvider="target"> <window key="window" title="Persephone" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="IQv-IB-iLA" customClass="MainWindow" customModule="Persephone" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/> <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/> <windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="207" y="570" width="960" height="560"/> <rect key="contentRect" x="207" y="570" width="960" height="560"/>
@ -289,7 +290,7 @@
<tabViewController title="General" selectedTabViewItemIndex="0" tabStyle="toolbar" id="zhe-qh-Mal" customClass="PreferencesViewController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController"> <tabViewController title="General" selectedTabViewItemIndex="0" tabStyle="toolbar" id="zhe-qh-Mal" customClass="PreferencesViewController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
<tabViewItems> <tabViewItems>
<tabViewItem label="General" identifier="generalPreferencesTab" image="NSPreferencesGeneral" id="kn0-fa-vM3"/> <tabViewItem label="General" identifier="generalPreferencesTab" image="NSPreferencesGeneral" id="kn0-fa-vM3"/>
<tabViewItem label="Album Art" identifier="albumArtPreferencesTab" image="coverArtPreferencesIcon" id="4Lj-dz-bOK"/> <tabViewItem label="Cover Art" identifier="coverArtPreferencesTab" image="coverArtPreferencesIcon" id="4Lj-dz-bOK"/>
</tabViewItems> </tabViewItems>
<viewControllerTransitionOptions key="transitionOptions" allowUserInteraction="YES"/> <viewControllerTransitionOptions key="transitionOptions" allowUserInteraction="YES"/>
<tabView key="tabView" type="noTabsNoBorder" id="6dC-M0-oC5"> <tabView key="tabView" type="noTabsNoBorder" id="6dC-M0-oC5">
@ -310,10 +311,10 @@
</objects> </objects>
<point key="canvasLocation" x="916" y="236"/> <point key="canvasLocation" x="916" y="236"/>
</scene> </scene>
<!--Album Art--> <!--Cover Art-->
<scene sceneID="pQx-0G-WVt"> <scene sceneID="pQx-0G-WVt">
<objects> <objects>
<viewController title="Album Art" id="3C9-vU-zjZ" customClass="AlbumArtPrefsController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController"> <viewController title="Cover Art" id="3C9-vU-zjZ" userLabel="Cover Art" customClass="CoverArtPrefsController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="PyK-v2-kus"> <view key="view" id="PyK-v2-kus">
<rect key="frame" x="0.0" y="0.0" width="524" height="168"/> <rect key="frame" x="0.0" y="0.0" width="524" height="168"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
@ -527,7 +528,7 @@
</constraints> </constraints>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="QUEUE" id="Mqf-uh-ibl"> <textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="QUEUE" id="Mqf-uh-ibl">
<font key="font" metaFont="smallSystemBold"/> <font key="font" metaFont="smallSystemBold"/>
<color key="textColor" name="controlAccentColor" catalog="System" colorSpace="catalog"/> <color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell> </textFieldCell>
</textField> </textField>
@ -635,9 +636,9 @@
<rect key="frame" x="0.0" y="220" width="328" height="328"/> <rect key="frame" x="0.0" y="220" width="328" height="328"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews> <subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="Dw3-M5-tWY"> <imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="Dw3-M5-tWY" customClass="CurrentCoverArtView" customModule="Persephone" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="328" height="328"/> <rect key="frame" x="0.0" y="0.0" width="328" height="328"/>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="blankAlbum" id="IoN-3N-TCb"/> <imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="defaultCoverArt" id="IoN-3N-TCb"/>
</imageView> </imageView>
</subviews> </subviews>
<constraints> <constraints>
@ -658,7 +659,7 @@
</holdingPriorities> </holdingPriorities>
</splitView> </splitView>
<connections> <connections>
<outlet property="queueAlbumArtImage" destination="Dw3-M5-tWY" id="3hQ-Gu-XqM"/> <outlet property="queueCoverArtImage" destination="Dw3-M5-tWY" id="3hQ-Gu-XqM"/>
<outlet property="queueView" destination="jEJ-jg-fll" id="cwo-E8-deo"/> <outlet property="queueView" destination="jEJ-jg-fll" id="cwo-E8-deo"/>
</connections> </connections>
</viewController> </viewController>
@ -720,8 +721,8 @@
</scenes> </scenes>
<resources> <resources>
<image name="NSPreferencesGeneral" width="32" height="32"/> <image name="NSPreferencesGeneral" width="32" height="32"/>
<image name="blankAlbum" width="128" height="128"/>
<image name="coverArtPreferencesIcon" width="32" height="32"/> <image name="coverArtPreferencesIcon" width="32" height="32"/>
<image name="defaultCoverArt" width="128" height="128"/>
<image name="nextTrackButton" width="17" height="17"/> <image name="nextTrackButton" width="17" height="17"/>
<image name="playButton" width="17" height="17"/> <image name="playButton" width="17" height="17"/>
<image name="prevTrackButton" width="17" height="17"/> <image name="prevTrackButton" width="17" height="17"/>

View File

@ -1,16 +1,15 @@
// //
// AlbumArtService.swift // CoverArtService.swift
// Persephone // Persephone
// //
// Created by Daniel Barber on 2019/2/23. // Created by Daniel Barber on 2019/2/23.
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
import PromiseKit import PromiseKit
class AlbumArtService { class CoverArtService {
var preferences = Preferences()
let song: Song let song: Song
let album: Album let album: Album
@ -20,14 +19,14 @@ class AlbumArtService {
let bigArtworkSize = 600 let bigArtworkSize = 600
var session = URLSession(configuration: .default) var session = URLSession(configuration: .default)
let artworkQueue = DispatchQueue(label: "albumArtQueue", qos: .utility) let coverArtQueue = DispatchQueue(label: "coverArtQueue", qos: .utility)
init(song: Song) { init(song: Song) {
self.song = song self.song = song
self.album = song.album self.album = song.album
} }
func fetchBigAlbumArt() -> Promise<NSImage?> { func fetchBigCoverArt() -> Promise<NSImage?> {
return firstly { return firstly {
self.getArtworkFromFilesystem() self.getArtworkFromFilesystem()
}.then { (image: NSImage?) -> Promise<NSImage?> in }.then { (image: NSImage?) -> Promise<NSImage?> in
@ -37,14 +36,14 @@ class AlbumArtService {
} }
} }
func fetchAlbumArt() -> Guarantee<NSImage?> { func fetchCoverArt() -> Guarantee<NSImage?> {
return firstly { return firstly {
self.getCachedArtwork() self.getCachedArtwork()
}.then { (artwork: NSImage?) -> Promise<NSImage?> in }.then { (artwork: NSImage?) -> Promise<NSImage?> in
artwork.map(Promise.value) ?? self.getArtworkFromFilesystem() artwork.map(Promise.value) ?? self.getArtworkFromFilesystem()
}.then { (artwork: NSImage?) -> Promise<NSImage?> in }.then { (artwork: NSImage?) -> Promise<NSImage?> in
artwork.map(Promise.value) ?? self.getRemoteArtwork() artwork.map(Promise.value) ?? self.getRemoteArtwork()
}.compactMap(on: artworkQueue) { }.compactMap(on: coverArtQueue) {
return self.sizeAndCacheImage($0).map(Optional.some) return self.sizeAndCacheImage($0).map(Optional.some)
}.recover { _ in }.recover { _ in
return .value(nil) return .value(nil)

View File

@ -1,22 +1,22 @@
// //
// AlbumArtService+Caching.swift // CoverArtService+Caching.swift
// Persephone // Persephone
// //
// Created by Daniel Barber on 2019/3/17. // Created by Daniel Barber on 2019/3/17.
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
import PromiseKit import PromiseKit
extension AlbumArtService { extension CoverArtService {
static let cacheDir = try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent(Bundle.main.bundleIdentifier!) static let cacheDir = try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent(Bundle.main.bundleIdentifier!)
func getCachedArtwork() -> Promise<NSImage?> { func getCachedArtwork() -> Promise<NSImage?> {
return Promise { seal in return Promise { seal in
artworkQueue.async { coverArtQueue.async {
if self.isArtworkCached() { if self.isArtworkCached() {
let cacheFilePath = AlbumArtService.cacheDir.appendingPathComponent(self.album.hash).path let cacheFilePath = CoverArtService.cacheDir.appendingPathComponent(self.album.hash).path
let data = FileManager.default.contents(atPath: cacheFilePath) let data = FileManager.default.contents(atPath: cacheFilePath)
let image = NSImage(data: data ?? Data()) ?? NSImage.defaultCoverArt let image = NSImage(data: data ?? Data()) ?? NSImage.defaultCoverArt
@ -29,7 +29,7 @@ extension AlbumArtService {
} }
func cacheArtwork(data: Data?) { func cacheArtwork(data: Data?) {
artworkQueue.async { coverArtQueue.async {
guard let bundleIdentifier = Bundle.main.bundleIdentifier, guard let bundleIdentifier = Bundle.main.bundleIdentifier,
let cacheDir = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true) let cacheDir = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent(bundleIdentifier) .appendingPathComponent(bundleIdentifier)
@ -44,7 +44,7 @@ extension AlbumArtService {
} }
func isArtworkCached() -> Bool { func isArtworkCached() -> Bool {
let cacheFilePath = AlbumArtService.cacheDir.appendingPathComponent(album.hash).path let cacheFilePath = CoverArtService.cacheDir.appendingPathComponent(album.hash).path
return FileManager.default.fileExists(atPath: cacheFilePath) return FileManager.default.fileExists(atPath: cacheFilePath)
} }

View File

@ -1,15 +1,15 @@
// //
// AlbumArtService+Filesystem.swift // CoverArtService+Filesystem.swift
// Persephone // Persephone
// //
// Created by Daniel Barber on 2019/3/17. // Created by Daniel Barber on 2019/3/17.
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
import PromiseKit import PromiseKit
extension AlbumArtService { extension CoverArtService {
var coverArtFilenames: [String] { var coverArtFilenames: [String] {
return [ return [
"folder.jpg", "folder.jpg",
@ -19,12 +19,12 @@ extension AlbumArtService {
} }
var musicDir: String { var musicDir: String {
return self.preferences.expandedMpdLibraryDir return AppDelegate.store.state.preferencesState.expandedMpdLibraryDir
} }
func getArtworkFromFilesystem() -> Promise<NSImage?> { func getArtworkFromFilesystem() -> Promise<NSImage?> {
return Promise { seal in return Promise { seal in
artworkQueue.async { coverArtQueue.async {
guard let artworkPath = self.fileSystemArtworkFilePath() guard let artworkPath = self.fileSystemArtworkFilePath()
else { seal.fulfill(nil); return } else { seal.fulfill(nil); return }
@ -48,7 +48,7 @@ extension AlbumArtService {
} }
func fileSystemArtworkFilePath() -> String? { func fileSystemArtworkFilePath() -> String? {
let musicDir = self.preferences.expandedMpdLibraryDir let musicDir = AppDelegate.store.state.preferencesState.expandedMpdLibraryDir
return self.coverArtFilenames return self.coverArtFilenames
.lazy .lazy

View File

@ -1,17 +1,17 @@
// //
// AlbumArtService+Remote.swift // CoverArtService+Remote.swift
// Persephone // Persephone
// //
// Created by Daniel Barber on 2019/3/17. // Created by Daniel Barber on 2019/3/17.
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
import SwiftyJSON import SwiftyJSON
import PromiseKit import PromiseKit
import PMKFoundation import PMKFoundation
extension AlbumArtService { extension CoverArtService {
enum RemoteArtworkError: Error { enum RemoteArtworkError: Error {
case noArtworkAvailable case noArtworkAvailable
case notConfigured case notConfigured
@ -19,13 +19,13 @@ extension AlbumArtService {
func getRemoteArtwork() -> Promise<NSImage?> { func getRemoteArtwork() -> Promise<NSImage?> {
return Promise { seal in return Promise { seal in
if preferences.fetchMissingArtworkFromInternet { if AppDelegate.store.state.preferencesState .fetchMissingArtworkFromInternet {
artworkQueue.async { coverArtQueue.async {
let albumArtWorkItem = DispatchWorkItem { let coverArtWorkItem = DispatchWorkItem {
self.getArtworkFromMusicBrainz().map(Optional.some).pipe(to: seal.resolve) self.getArtworkFromMusicBrainz().map(Optional.some).pipe(to: seal.resolve)
} }
AlbumArtQueue.shared.addToQueue(workItem: albumArtWorkItem) CoverArtQueue.shared.addToQueue(workItem: coverArtWorkItem)
} }
} else { } else {
throw RemoteArtworkError.notConfigured throw RemoteArtworkError.notConfigured

View File

@ -6,7 +6,7 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
struct SongNotifierService { struct SongNotifierService {
let song: Song let song: Song

View File

@ -0,0 +1,21 @@
//
// AlbumListActions.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/28.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import AppKit
import ReSwift
struct ResetAlbumListCoverArtAction: Action {}
struct UpdateCoverArtAction: Action {
var coverArt: NSImage?
var albumIndex: Int
}
struct UpdateAlbumListAction: Action {
var albums: [MPDClient.MPDAlbum]
}

View File

@ -0,0 +1,30 @@
//
// PlayerActions.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/28.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import AppKit
import ReSwift
struct UpdateCurrentCoverArtAction: Action {
var coverArt: NSImage?
}
struct UpdateCurrentSongAction: Action {
var currentSong: Song
}
struct UpdateElapsedTimeAction: Action {
var elapsedTimeMs: UInt = 0
}
struct UpdateStatusAction: Action {
var status: MPDClient.MPDStatus
}
struct StartedDatabaseUpdateAction: Action {}
struct FinishedDatabaseUpdateAction: Action {}

View File

@ -0,0 +1,27 @@
//
// PreferencesActions.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/28.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import ReSwift
struct UpdateServerHost: Action {
var host: String?
}
struct UpdateServerPort: Action {
var port: Int?
}
struct UpdateMPDLibraryDir: Action {
var mpdLibraryDir: String?
}
struct UpdateFetchMissingArtworkFromInternet: Action {
var fetchMissingArtworkFromInternet: Bool
}
struct SavePreferences: Action {}

View File

@ -0,0 +1,21 @@
//
// QueueActions.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/28.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import ReSwift
struct UpdateQueueAction: Action {
var queue: [MPDClient.MPDSong]
}
struct UpdateQueuePosAction: Action {
var queuePos: Int
}
struct UpdateQueuePlayerStateAction: Action {
var state: MPDClient.MPDStatus.State?
}

View File

@ -0,0 +1,19 @@
//
// AlbumListState.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/19.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import ReSwift
struct AlbumListState: StateType {
var albums: [Album] = []
}
extension AlbumListState: Equatable {
static func == (lhs: AlbumListState, rhs: AlbumListState) -> Bool {
return lhs.albums == rhs.albums
}
}

View File

@ -0,0 +1,16 @@
//
// AppState.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/18.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import ReSwift
struct AppState: StateType {
var playerState = PlayerState()
var queueState = QueueState()
var albumListState = AlbumListState()
var preferencesState = PreferencesState()
}

View File

@ -0,0 +1,32 @@
//
// PlayerState.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/19.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import AppKit
import ReSwift
struct PlayerState: StateType {
var status: MPDClient.MPDStatus?
var currentSong: Song?
var currentArtwork: NSImage?
var state: MPDClient.MPDStatus.State?
var totalTime: UInt?
var elapsedTimeMs: UInt?
var databaseUpdating: Bool = false
}
extension PlayerState: Equatable {
static func == (lhs: PlayerState, rhs: PlayerState) -> Bool {
return (lhs.state == rhs.state) &&
(lhs.totalTime == rhs.totalTime) &&
(lhs.elapsedTimeMs == rhs.elapsedTimeMs) &&
(lhs.databaseUpdating == rhs.databaseUpdating)
}
}

View File

@ -0,0 +1,51 @@
//
// PreferencesState.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/28.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
import ReSwift
struct PreferencesState: StateType {
let preferences = UserDefaults.standard
var mpdServer: MPDServer
let mpdLibraryDirDefault = "~/Music"
var mpdLibraryDir: String?
var mpdLibraryDirOrDefault: String {
return mpdLibraryDir ?? mpdLibraryDirDefault
}
var expandedMpdLibraryDir: String {
return NSString(string: mpdLibraryDirOrDefault).expandingTildeInPath
}
var fetchMissingArtworkFromInternet: Bool
init() {
self.mpdServer = MPDServer(
host: preferences.string(forKey: "mpdHost"),
port: preferences.value(forKey: "mpdPort") as? Int
)
self.mpdLibraryDir = preferences.string(forKey: "mpdLibraryDir")
self.fetchMissingArtworkFromInternet = preferences.bool(
forKey: "fetchMissingArtworkFromInternet"
)
}
func save() {
preferences.set(mpdServer.host, forKey: "mpdHost")
if (mpdServer.port.map { $0 > 0 } ?? false) {
preferences.set(mpdServer.port, forKey: "mpdPort")
} else {
preferences.removeObject(forKey: "mpdPort")
}
preferences.set(mpdLibraryDir, forKey: "mpdLibraryDir")
preferences.set(fetchMissingArtworkFromInternet, forKey: "fetchMissingArtworkFromInternet")
}
}

View File

@ -0,0 +1,24 @@
//
// QueueState.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/19.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import ReSwift
struct QueueState: StateType {
var queue: [QueueItem] = []
var queuePos: Int = -1
var state: MPDClient.MPDStatus.State?
}
extension QueueState: Equatable {
static func == (lhs: QueueState, rhs: QueueState) -> Bool {
return (lhs.queue == rhs.queue) &&
(lhs.queuePos == rhs.queuePos) &&
(lhs.state == rhs.state)
}
}

View File

@ -0,0 +1,41 @@
//
// AlbumListReducer.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/21.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import ReSwift
func albumListReducer(action: Action, state: AlbumListState?) -> AlbumListState {
var state = state ?? AlbumListState()
switch action {
case let action as UpdateAlbumListAction:
state.albums = action.albums.map { Album(mpdAlbum: $0) }
case let action as UpdateCoverArtAction:
state.albums[action.albumIndex].coverArt = .loaded(action.coverArt)
case is ResetAlbumListCoverArtAction:
state.albums = AppDelegate.store.state.albumListState.albums.map {
var album = $0
switch album.coverArt {
case .loaded(let coverArt):
if coverArt == nil {
album.coverArt = .notLoaded
}
default:
album.coverArt = .notLoaded
}
return album
}
default:
break
}
return state
}

View File

@ -0,0 +1,18 @@
//
// AppReducer.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/19.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import ReSwift
func appReducer(action: Action, state: AppState?) -> AppState {
return AppState(
playerState: playerReducer(action: action, state: state?.playerState),
queueState: queueReducer(action: action, state: state?.queueState),
albumListState: albumListReducer(action: action, state: state?.albumListState),
preferencesState: preferencesReducer(action: action, state: state?.preferencesState)
)
}

View File

@ -0,0 +1,74 @@
//
// PlayerReducer.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/19.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import AppKit
import ReSwift
func playerReducer(action: Action, state: PlayerState?) -> PlayerState {
var state = state ?? PlayerState()
switch action {
case let action as UpdateStatusAction:
state.status = action.status
state.state = action.status.state
state.totalTime = action.status.totalTime
state.elapsedTimeMs = action.status.elapsedTimeMs
if state.state == .playing {
AppDelegate.trackTimer.start(elapsedTimeMs: state.elapsedTimeMs)
} else {
AppDelegate.trackTimer.stop(elapsedTimeMs: state.elapsedTimeMs)
}
DispatchQueue.main.async {
AppDelegate.store.dispatch(
UpdateQueuePlayerStateAction(state: state.state)
)
}
case let action as UpdateCurrentSongAction:
state.currentSong = action.currentSong
if let currentSong = state.currentSong {
let coverArtService = CoverArtService(song: currentSong)
coverArtService.fetchBigCoverArt()
.done() { image in
DispatchQueue.main.async {
if let image = image {
AppDelegate.store.dispatch(UpdateCurrentCoverArtAction(coverArt: image))
} else {
AppDelegate.store.dispatch(UpdateCurrentCoverArtAction(coverArt: .defaultCoverArt))
}
}
}
.cauterize()
} else {
DispatchQueue.main.async {
AppDelegate.store.dispatch(UpdateCurrentCoverArtAction(coverArt: .defaultCoverArt))
}
}
case let action as UpdateCurrentCoverArtAction:
state.currentArtwork = action.coverArt
case let action as UpdateElapsedTimeAction:
state.elapsedTimeMs = action.elapsedTimeMs
case is StartedDatabaseUpdateAction:
state.databaseUpdating = true
case is FinishedDatabaseUpdateAction:
state.databaseUpdating = false
default:
break
}
return state
}

View File

@ -0,0 +1,34 @@
//
// PreferencesReducer.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/28.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import Foundation
import ReSwift
func preferencesReducer(action: Action, state: PreferencesState?) -> PreferencesState {
var state = state ?? PreferencesState()
switch action {
case let action as UpdateServerHost:
state.mpdServer.host = action.host
case let action as UpdateServerPort:
state.mpdServer.port = action.port
case let action as UpdateMPDLibraryDir:
state.mpdLibraryDir = action.mpdLibraryDir
case is SavePreferences:
state.save()
default:
break
}
return state
}

View File

@ -0,0 +1,55 @@
//
// QueueReducer.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/21.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import AppKit
import ReSwift
func queueReducer(action: Action, state: QueueState?) -> QueueState {
var state = state ?? QueueState()
switch action {
case let action as UpdateQueueAction:
state.queuePos = -1
state.queue = action.queue.enumerated().map { index, mpdSong in
let song = Song(mpdSong: mpdSong)
return QueueItem(
song: song,
queuePos: index,
isPlaying: index == state.queuePos
)
}
case let action as UpdateQueuePosAction:
let oldSongRowPos = state.queuePos
let newSongRowPos = action.queuePos
state.queuePos = action.queuePos
if oldSongRowPos >= 0 {
state.queue[oldSongRowPos].isPlaying = false
}
if newSongRowPos >= 0 {
state.queue[newSongRowPos].isPlaying = true
}
DispatchQueue.main.async {
AppDelegate.store.dispatch(
UpdateCurrentSongAction(currentSong: state.queue[newSongRowPos].song)
)
}
case let action as UpdateQueuePlayerStateAction:
state.state = action.state
default:
break
}
return state
}

View File

@ -6,7 +6,7 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
class AlbumItemView: NSView { class AlbumItemView: NSView {
var trackingArea: NSTrackingArea? var trackingArea: NSTrackingArea?

View File

@ -0,0 +1,32 @@
//
// CurrentCoverArtView.swift
// Persephone
//
// Created by Daniel Barber on 2019/4/27.
// Copyright © 2019 Dan Barber. All rights reserved.
//
import AppKit
import ReSwift
class CurrentCoverArtView: NSImageView {
required init?(coder: NSCoder) {
super.init(coder: coder)
AppDelegate.store.subscribe(self) {
$0.select { $0.playerState.currentArtwork }
}
}
}
extension CurrentCoverArtView: StoreSubscriber {
typealias StoreSubscriberStateType = NSImage?
func newState(state: NSImage?) {
if let coverArt = state {
image = coverArt
} else {
image = .defaultCoverArt
}
}
}

View File

@ -6,7 +6,7 @@
// Copyright © 2019 Dan Barber. All rights reserved. // Copyright © 2019 Dan Barber. All rights reserved.
// //
import Cocoa import AppKit
class MainWindow: NSWindow { class MainWindow: NSWindow {
override func keyDown(with event: NSEvent) { override func keyDown(with event: NSEvent) {