From 89320037b723d4e82319bd213e74f528014cb012 Mon Sep 17 00:00:00 2001 From: Daniel Barber Date: Sun, 29 Sep 2019 18:30:07 -0400 Subject: [PATCH] Show list of artists --- Persephone.xcodeproj/project.pbxproj | 58 ++++++++++++------- Persephone/App.swift | 2 +- Persephone/AppDelegate.swift | 7 ++- .../Controllers/AlbumViewController.swift | 13 +++++ .../Controllers/ArtistViewController.swift | 46 +++++++++++++++ Persephone/Controllers/ArtistViewItem.swift | 3 +- .../Controllers/MPDServerController.swift | 11 ++-- .../Controllers/MPDServerDelegate.swift | 14 +++-- .../Controllers/QueueViewController.swift | 18 ++++++ Persephone/Controllers/WindowController.swift | 2 +- Persephone/DataSources/ArtistDataSource.swift | 27 +++++++++ .../NSUserInterfaceItemIdentifier.swift | 1 + Persephone/Extensions/Notification.swift | 21 +------ Persephone/Layouts/ArtistViewLayout.swift | 22 +++++++ .../Extensions/MPDClient+Artist.swift | 38 ++++++++++++ .../Extensions/MPDClient+Command.swift | 5 ++ .../Extensions/MPDClient+Connection.swift | 16 ++--- .../Extensions/MPDClient+Queue.swift | 4 +- Persephone/MPDClient/MPDClient.swift | 5 +- Persephone/MPDClient/Models/MPDCommand.swift | 3 + Persephone/MPDClient/Protocols/Delegate.swift | 2 + Persephone/Resources/AlbumViewItem.xib | 6 +- Persephone/Resources/ArtistViewItem.xib | 9 +-- .../Resources/Base.lproj/Main.storyboard | 10 ++-- .../State/Actions/ArtistListActions.swift | 14 +++++ Persephone/State/AppState.swift | 1 + Persephone/State/Reducers/AppReducer.swift | 1 + Persephone/State/Reducers/ArtistReducer.swift | 25 ++++++++ Persephone/Views/ArtistItemView.swift | 15 +++++ 29 files changed, 322 insertions(+), 77 deletions(-) create mode 100644 Persephone/DataSources/ArtistDataSource.swift create mode 100644 Persephone/Layouts/ArtistViewLayout.swift create mode 100644 Persephone/MPDClient/Extensions/MPDClient+Artist.swift create mode 100644 Persephone/State/Actions/ArtistListActions.swift create mode 100644 Persephone/State/Reducers/ArtistReducer.swift create mode 100644 Persephone/Views/ArtistItemView.swift diff --git a/Persephone.xcodeproj/project.pbxproj b/Persephone.xcodeproj/project.pbxproj index 78aabe2..64da2ec 100644 --- a/Persephone.xcodeproj/project.pbxproj +++ b/Persephone.xcodeproj/project.pbxproj @@ -119,6 +119,12 @@ E4E96D13233E630800AFD36F /* PMKFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = E4E96D12233E630800AFD36F /* PMKFoundation */; }; E4EB2379220F10B8008C70C0 /* MPDPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4EB2378220F10B8008C70C0 /* MPDPair.swift */; }; E4EB237B220F7CF1008C70C0 /* MPDAlbum.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4EB237A220F7CF1008C70C0 /* MPDAlbum.swift */; }; + E4F26F732341166200D45FF9 /* ArtistDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F26F722341166200D45FF9 /* ArtistDataSource.swift */; }; + E4F26F75234117D600D45FF9 /* ArtistItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F26F74234117D600D45FF9 /* ArtistItemView.swift */; }; + E4F26F7723411AE300D45FF9 /* ArtistListActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F26F7623411AE300D45FF9 /* ArtistListActions.swift */; }; + E4F26F7923411B1500D45FF9 /* ArtistReducer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F26F7823411B1500D45FF9 /* ArtistReducer.swift */; }; + E4F26F7B23411D5400D45FF9 /* MPDClient+Artist.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F26F7A23411D5400D45FF9 /* MPDClient+Artist.swift */; }; + E4F26F7D2341441C00D45FF9 /* ArtistViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F26F7C2341441C00D45FF9 /* ArtistViewLayout.swift */; }; E4F6B460221E119B00ACF42A /* QueueDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */; }; E4F6B463221E125900ACF42A /* QueueItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B462221E125900ACF42A /* QueueItem.swift */; }; E4F6B467221E233200ACF42A /* AlbumDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4F6B466221E233200ACF42A /* AlbumDataSource.swift */; }; @@ -317,6 +323,12 @@ E4E8CC9922075D370024217A /* MPDSong.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDSong.swift; sourceTree = ""; }; E4EB2378220F10B8008C70C0 /* MPDPair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDPair.swift; sourceTree = ""; }; E4EB237A220F7CF1008C70C0 /* MPDAlbum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDAlbum.swift; sourceTree = ""; }; + E4F26F722341166200D45FF9 /* ArtistDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArtistDataSource.swift; sourceTree = ""; }; + E4F26F74234117D600D45FF9 /* ArtistItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArtistItemView.swift; sourceTree = ""; }; + E4F26F7623411AE300D45FF9 /* ArtistListActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArtistListActions.swift; sourceTree = ""; }; + E4F26F7823411B1500D45FF9 /* ArtistReducer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArtistReducer.swift; sourceTree = ""; }; + E4F26F7A23411D5400D45FF9 /* MPDClient+Artist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MPDClient+Artist.swift"; sourceTree = ""; }; + E4F26F7C2341441C00D45FF9 /* ArtistViewLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArtistViewLayout.swift; sourceTree = ""; }; E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueDataSource.swift; sourceTree = ""; }; E4F6B462221E125900ACF42A /* QueueItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueItem.swift; sourceTree = ""; }; E4F6B466221E233200ACF42A /* AlbumDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDataSource.swift; sourceTree = ""; }; @@ -448,6 +460,7 @@ isa = PBXGroup; children = ( E41E530A223C033700173814 /* MPDClient+Album.swift */, + E4F26F7A23411D5400D45FF9 /* MPDClient+Artist.swift */, E41E5308223C020400173814 /* MPDClient+Command.swift */, E41E52FC223BF87300173814 /* MPDClient+Connection.swift */, E42410B52241B956005ED6DF /* MPDClient+Database.swift */, @@ -465,16 +478,17 @@ E408D3C3220E138B0006D9BE /* Views */ = { isa = PBXGroup; children = ( + E43AC1F722C7065A001E483C /* AlbumCoverButton.swift */, E4A3A6A022A457B600EA2C40 /* AlbumDetailSongListView.swift */, E45878372296173C00586A1C /* AlbumDetailSongRowView.swift */, E47E2FD62220720300F747E6 /* AlbumItemView.swift */, + E4F26F74234117D600D45FF9 /* ArtistItemView.swift */, + E4BBD2E823356DC100702C16 /* BrowseViewButton.swift */, E4B11BB7227538FA0075461B /* CurrentCoverArtView.swift */, E489E3A222B9D31800CA8CBD /* DraggedSongView.swift */, E47E2FD222205D2500F747E6 /* MainWindow.swift */, E423563F228623D2001216D6 /* QueueSongTitleView.swift */, E4120D6B22AD8139004CB1F8 /* QueueView.swift */, - E43AC1F722C7065A001E483C /* AlbumCoverButton.swift */, - E4BBD2E823356DC100702C16 /* BrowseViewButton.swift */, ); path = Views; sourceTree = ""; @@ -560,6 +574,7 @@ isa = PBXGroup; children = ( E47E2FE42220AA0700F747E6 /* AlbumViewLayout.swift */, + E4F26F7C2341441C00D45FF9 /* ArtistViewLayout.swift */, ); path = Layouts; sourceTree = ""; @@ -607,11 +622,12 @@ E4B11B5F226A4BED0075461B /* Reducers */ = { isa = PBXGroup; children = ( - E4B11B60226A4BFF0075461B /* PlayerReducer.swift */, - E4B11B62226A4C510075461B /* AppReducer.swift */, - E4B11B74226CC4D30075461B /* QueueReducer.swift */, E4B11B78226D346B0075461B /* AlbumListReducer.swift */, + E4B11B62226A4C510075461B /* AppReducer.swift */, + E4F26F7823411B1500D45FF9 /* ArtistReducer.swift */, + E4B11B60226A4BFF0075461B /* PlayerReducer.swift */, E4FF718F227601B400D4C412 /* PreferencesReducer.swift */, + E4B11B74226CC4D30075461B /* QueueReducer.swift */, E440519D227BB0720090CD6F /* UIReducer.swift */, ); path = Reducers; @@ -621,14 +637,14 @@ isa = PBXGroup; children = ( E4B11B6B226A5AF50075461B /* Actions */, - E4B11B5F226A4BED0075461B /* Reducers */, - E4B11B52226928F20075461B /* AppState.swift */, - E440519F227BB0AB0090CD6F /* UIState.swift */, - E4B11B65226A4F830075461B /* PlayerState.swift */, - E4B11B67226A4FA00075461B /* QueueState.swift */, E4B11B69226A4FBC0075461B /* AlbumListState.swift */, - E4FF718D2276010E00D4C412 /* PreferencesState.swift */, + E4B11B52226928F20075461B /* AppState.swift */, E4BBD2F223357C0700702C16 /* ArtistListState.swift */, + E4B11B65226A4F830075461B /* PlayerState.swift */, + E4FF718D2276010E00D4C412 /* PreferencesState.swift */, + E4B11B67226A4FA00075461B /* QueueState.swift */, + E4B11B5F226A4BED0075461B /* Reducers */, + E440519F227BB0AB0090CD6F /* UIState.swift */, ); path = State; sourceTree = ""; @@ -637,9 +653,10 @@ isa = PBXGroup; children = ( E4B11BC12275EE410075461B /* AlbumListActions.swift */, + E4F26F7623411AE300D45FF9 /* ArtistListActions.swift */, E4B11BBD2275EDAA0075461B /* PlayerActions.swift */, - E4B11BBF2275EE150075461B /* QueueActions.swift */, E4FF71912276029000D4C412 /* PreferencesActions.swift */, + E4B11BBF2275EE150075461B /* QueueActions.swift */, E440519B227BAF2E0090CD6F /* UIActions.swift */, ); path = Actions; @@ -707,6 +724,7 @@ E4F6B466221E233200ACF42A /* AlbumDataSource.swift */, E4F6B45F221E119B00ACF42A /* QueueDataSource.swift */, E43B67AC229194CD007DCF55 /* AlbumTracksDataSource.swift */, + E4F26F722341166200D45FF9 /* ArtistDataSource.swift */, ); path = DataSources; sourceTree = ""; @@ -930,6 +948,7 @@ E4B11B68226A4FA00075461B /* QueueState.swift in Sources */, E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */, E4BBD2ED2335798E00702C16 /* ArtistViewController.swift in Sources */, + E4F26F732341166200D45FF9 /* ArtistDataSource.swift in Sources */, E4D3BFA622B419C000C56F48 /* QueueViewController+NSOutlineViewDelegate.swift in Sources */, E4405192227644340090CD6F /* MPDServerController.swift in Sources */, E4C8B53E22349002009A20F3 /* MPDIdle.swift in Sources */, @@ -950,6 +969,7 @@ E43B67AD229194CD007DCF55 /* AlbumTracksDataSource.swift in Sources */, E41E52FD223BF87300173814 /* MPDClient+Connection.swift in Sources */, E450AD7E222620A10091BED3 /* Album.swift in Sources */, + E4F26F7D2341441C00D45FF9 /* ArtistViewLayout.swift in Sources */, E408D3B6220DD8970006D9BE /* Notification.swift in Sources */, E43AC1F822C7065A001E483C /* AlbumCoverButton.swift in Sources */, E45962C62241A78500FC1A1E /* MPDCommand.swift in Sources */, @@ -963,6 +983,7 @@ E4F6B463221E125900ACF42A /* QueueItem.swift in Sources */, E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */, E4B11BC22275EE410075461B /* AlbumListActions.swift in Sources */, + E4F26F7B23411D5400D45FF9 /* MPDClient+Artist.swift in Sources */, E4B11B61226A4C000075461B /* PlayerReducer.swift in Sources */, E4FF71922276029000D4C412 /* PreferencesActions.swift in Sources */, E489E39922B85D0400CA8CBD /* NSPasteboard.swift in Sources */, @@ -980,11 +1001,13 @@ E4B11B75226CC4D30075461B /* QueueReducer.swift in Sources */, E41E5309223C020400173814 /* MPDClient+Command.swift in Sources */, E4BBD2E923356DC100702C16 /* BrowseViewButton.swift in Sources */, + E4F26F7923411B1500D45FF9 /* ArtistReducer.swift in Sources */, E4B11B73226A6C770075461B /* TrackTimer.swift in Sources */, E4BBD2EB2335735500702C16 /* BrowseController.swift in Sources */, E44051942278765A0090CD6F /* App.swift in Sources */, E4B11B79226D346B0075461B /* AlbumListReducer.swift in Sources */, E47E2FE52220AA0700F747E6 /* AlbumViewLayout.swift in Sources */, + E4F26F75234117D600D45FF9 /* ArtistItemView.swift in Sources */, E41E52FF223BF95E00173814 /* MPDClient+Transport.swift in Sources */, E47E2FD322205D2500F747E6 /* MainWindow.swift in Sources */, E47E2FD122205C4600F747E6 /* MainSplitViewController.swift in Sources */, @@ -999,6 +1022,7 @@ E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */, E4B11B66226A4F830075461B /* PlayerState.swift in Sources */, E4B11BBE2275EDAA0075461B /* PlayerActions.swift in Sources */, + E4F26F7723411AE300D45FF9 /* ArtistListActions.swift in Sources */, E4FF71942276043A00D4C412 /* MPDServer.swift in Sources */, E41E530E223EF4CF00173814 /* CoverArtService+Caching.swift in Sources */, E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */, @@ -1186,10 +1210,7 @@ COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = BDEE7ZBFZ3; ENABLE_HARDENED_RUNTIME = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/Mac", - ); + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Persephone/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -1217,10 +1238,7 @@ COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = BDEE7ZBFZ3; ENABLE_HARDENED_RUNTIME = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Carthage/Build/Mac", - ); + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Persephone/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/Persephone/App.swift b/Persephone/App.swift index f3e493e..6c99ff7 100644 --- a/Persephone/App.swift +++ b/Persephone/App.swift @@ -15,5 +15,5 @@ struct App { static let userNotificationsController = UserNotificationsController() static let mpdServerController = MPDServerController() static let mpdServerDelegate = MPDServerDelegate() - static let mpdClient = MPDClient(withDelegate: mpdServerDelegate) + static var mpdClient: MPDClient! } diff --git a/Persephone/AppDelegate.swift b/Persephone/AppDelegate.swift index 66ba9ab..cf46953 100644 --- a/Persephone/AppDelegate.swift +++ b/Persephone/AppDelegate.swift @@ -39,10 +39,13 @@ class AppDelegate: NSObject, func connectToMPDServer() { let mpdServer = App.store.state.preferencesState.mpdServer - App.mpdClient.connect( + App.mpdClient = MPDClient( host: mpdServer.hostOrDefault, - port: mpdServer.portOrDefault + port: mpdServer.portOrDefault, + withDelegate: App.mpdServerDelegate ) + + App.mpdClient.connect() } func instantiateControllers() { diff --git a/Persephone/Controllers/AlbumViewController.swift b/Persephone/Controllers/AlbumViewController.swift index 6dc83d8..39a90cb 100644 --- a/Persephone/Controllers/AlbumViewController.swift +++ b/Persephone/Controllers/AlbumViewController.swift @@ -21,6 +21,9 @@ class AlbumViewController: NSViewController, $0.select { $0.albumListState } } + NotificationCenter.default.addObserver(self, selector: #selector(didConnect), name: .didConnect, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(willDisconnect), name: .willDisconnect, object: nil) + albumScrollView.postsBoundsChangedNotifications = true albumCollectionView.dataSource = dataSource @@ -51,6 +54,16 @@ class AlbumViewController: NSViewController, layout.setScrollPosition() } + @objc func didConnect() { + App.mpdClient.fetchAllAlbums() + } + + @objc func willDisconnect() { + DispatchQueue.main.async { + App.store.dispatch(UpdateAlbumListAction(albums: [])) + } + } + @IBOutlet var albumScrollView: NSScrollView! @IBOutlet var albumCollectionView: NSCollectionView! } diff --git a/Persephone/Controllers/ArtistViewController.swift b/Persephone/Controllers/ArtistViewController.swift index b274021..57db6b6 100644 --- a/Persephone/Controllers/ArtistViewController.swift +++ b/Persephone/Controllers/ArtistViewController.swift @@ -7,7 +7,53 @@ // import AppKit +import ReSwift +import Differ class ArtistViewController: NSViewController { + var dataSource = ArtistDataSource() + + @IBOutlet var artistCollectionView: NSCollectionView! + override func viewDidLoad() { + super.viewDidLoad() + + App.store.subscribe(self) { + $0.select { $0.artistListState } + } + + NotificationCenter.default.addObserver(self, selector: #selector(didConnect), name: .didConnect, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(willDisconnect), name: .willDisconnect, object: nil) + + artistCollectionView.dataSource = dataSource + } + + deinit { + App.store.unsubscribe(self) + } + + @objc func didConnect() { + App.mpdClient.fetchAllArtists() + } + + @objc func willDisconnect() { + DispatchQueue.main.async { + App.store.dispatch(UpdateArtistListAction(artists: [])) + } + } +} + +extension ArtistViewController: StoreSubscriber { + typealias StoreSubscriberStateType = ArtistListState + + func newState(state: StoreSubscriberStateType) { + let oldArtists = dataSource.artists + + dataSource.artists = state.artists + + artistCollectionView.animateItemChanges( + oldData: oldArtists, + newData: dataSource.artists + ) + } } diff --git a/Persephone/Controllers/ArtistViewItem.swift b/Persephone/Controllers/ArtistViewItem.swift index 9119fad..8a40c99 100644 --- a/Persephone/Controllers/ArtistViewItem.swift +++ b/Persephone/Controllers/ArtistViewItem.swift @@ -8,14 +8,13 @@ import Cocoa -class ArtistViewItem: NSViewController { +class ArtistViewItem: NSCollectionViewItem { var artist: String? @IBOutlet var artistName: NSTextField! override func viewDidLoad() { super.viewDidLoad() - // Do view setup here. } func setArtist(_ artist: String) { diff --git a/Persephone/Controllers/MPDServerController.swift b/Persephone/Controllers/MPDServerController.swift index b357071..a21e940 100644 --- a/Persephone/Controllers/MPDServerController.swift +++ b/Persephone/Controllers/MPDServerController.swift @@ -11,9 +11,9 @@ import ReSwift class MPDServerController { init() { - App.store.subscribe(self) { - $0.select { $0.preferencesState.mpdServer } - } +// App.store.subscribe(self) { +// $0.select { $0.preferencesState.mpdServer } +// } } } @@ -22,9 +22,6 @@ extension MPDServerController: StoreSubscriber { func newState(state: MPDServer) { App.mpdClient.disconnect() - App.mpdClient.connect( - host: state.hostOrDefault, - port: state.portOrDefault - ) + App.mpdClient.connect() } } diff --git a/Persephone/Controllers/MPDServerDelegate.swift b/Persephone/Controllers/MPDServerDelegate.swift index 404bf13..8c38114 100644 --- a/Persephone/Controllers/MPDServerDelegate.swift +++ b/Persephone/Controllers/MPDServerDelegate.swift @@ -9,12 +9,12 @@ import Foundation class MPDServerDelegate: MPDClientDelegate { - func didConnect(mpdClient: MPDClient) {} + func didConnect(mpdClient: MPDClient) { + NotificationCenter.default.post(name: .didConnect, object: nil) + } func willDisconnect(mpdClient: MPDClient) { - DispatchQueue.main.async { - App.store.dispatch(UpdateAlbumListAction(albums: [])) - } + NotificationCenter.default.post(name: .willDisconnect, object: nil) } func didUpdateStatus(mpdClient: MPDClient, status: MPDClient.MPDStatus) { @@ -52,4 +52,10 @@ class MPDServerDelegate: MPDClientDelegate { App.store.dispatch(UpdateAlbumListAction(albums: albums)) } } + + func didLoadArtists(mpdClient: MPDClient, artists: [String]) { + DispatchQueue.main.async { + App.store.dispatch(UpdateArtistListAction(artists: artists)) + } + } } diff --git a/Persephone/Controllers/QueueViewController.swift b/Persephone/Controllers/QueueViewController.swift index 36e2ac7..0f4df03 100644 --- a/Persephone/Controllers/QueueViewController.swift +++ b/Persephone/Controllers/QueueViewController.swift @@ -22,12 +22,19 @@ class QueueViewController: NSViewController { $0.select { $0.queueState } } + NotificationCenter.default.addObserver(self, selector: #selector(didConnect), name: .didConnect, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(willDisconnect), name: .willDisconnect, object: nil) + queueView.dataSource = dataSource queueView.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle queueView.registerForDraggedTypes([.songPasteboardType, .albumPasteboardType]) queueView.draggingDestinationFeedbackStyle = .regular } + deinit { + App.store.unsubscribe(self) + } + override func keyDown(with event: NSEvent) { switch event.keyCode { case NSEvent.keyCodeSpace: @@ -43,6 +50,17 @@ class QueueViewController: NSViewController { } } + @objc func didConnect() { + App.mpdClient.fetchQueue() + } + + @objc func willDisconnect() { + DispatchQueue.main.async { + App.store.dispatch(UpdateQueuePosAction(queuePos: -1)) + App.store.dispatch(UpdateQueueAction(queue: [])) + } + } + @IBAction func playTrack(_ sender: Any) { let queuePos = queueView.selectedRow - 1 diff --git a/Persephone/Controllers/WindowController.swift b/Persephone/Controllers/WindowController.swift index de56e58..dfda304 100644 --- a/Persephone/Controllers/WindowController.swift +++ b/Persephone/Controllers/WindowController.swift @@ -189,7 +189,7 @@ extension WindowController: NSWindowDelegate { extension WindowController: StoreSubscriber { typealias StoreSubscriberStateType = (playerState: PlayerState, uiState: UIState) - func newState(state: (playerState: PlayerState, uiState: UIState)) { + func newState(state: StoreSubscriberStateType) { DispatchQueue.main.async { self.setTransportControlState(state.playerState) self.setShuffleRepeatState(state.playerState) diff --git a/Persephone/DataSources/ArtistDataSource.swift b/Persephone/DataSources/ArtistDataSource.swift new file mode 100644 index 0000000..0024e2c --- /dev/null +++ b/Persephone/DataSources/ArtistDataSource.swift @@ -0,0 +1,27 @@ +// +// ArtistDataSource.swift +// Persephone +// +// Created by Daniel Barber on 2019/9/29. +// Copyright © 2019 Dan Barber. All rights reserved. +// + +import AppKit + +class ArtistDataSource: NSObject, NSCollectionViewDataSource { + var artists: [String] = [] + + func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { + return artists.count + } + + func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { + let item = collectionView.makeItem(withIdentifier: .artistViewItem, for: indexPath) + guard let artistViewItem = item as? ArtistViewItem + else { return item } + + artistViewItem.setArtist(artists[indexPath.item]) + + return artistViewItem + } +} diff --git a/Persephone/Extensions/NSUserInterfaceItemIdentifier.swift b/Persephone/Extensions/NSUserInterfaceItemIdentifier.swift index e1479be..1983e99 100644 --- a/Persephone/Extensions/NSUserInterfaceItemIdentifier.swift +++ b/Persephone/Extensions/NSUserInterfaceItemIdentifier.swift @@ -17,6 +17,7 @@ extension NSUserInterfaceItemIdentifier { static let queueSongTitle = NSUserInterfaceItemIdentifier("songTitleCell") static let albumViewItem = NSUserInterfaceItemIdentifier("AlbumViewItem") + static let artistViewItem = NSUserInterfaceItemIdentifier("ArtistViewItem") static let discNumber = NSUserInterfaceItemIdentifier("discNumberCell") static let trackNumber = NSUserInterfaceItemIdentifier("trackNumberCell") diff --git a/Persephone/Extensions/Notification.swift b/Persephone/Extensions/Notification.swift index d3e2d2c..47c1a72 100644 --- a/Persephone/Extensions/Notification.swift +++ b/Persephone/Extensions/Notification.swift @@ -8,22 +8,7 @@ import Foundation -extension Notification { - static let didConnect = Name("MPDClientDidConnect") - static let willDisconnect = Name("MPDClientWillDisconnect") - - static let stateChanged = Name("MPDClientStateChanged") - static let timeChanged = Name("MPDClientTimeChanged") - static let databaseUpdateStarted = Name("MPDClientDatabaseUpdateStarted") - static let databaseUpdateFinished = Name("MPDClientDatabaseUpdateFinished") - static let queueChanged = Name("MPDClientQueueChanged") - static let queuePosChanged = Name("MPDClientQueuePosChanged") - static let loadedAlbums = Name("MPDClientLoadedAlbums") - - static let stateKey = "state" - static let queueKey = "queue" - static let queuePosKey = "song" - static let albumsKey = "albums" - static let totalTimeKey = "totalTime" - static let elapsedTimeMsKey = "elapsedTimeMs" +extension Notification.Name { + static let didConnect = Notification.Name("MPDClientDidConnect") + static let willDisconnect = Notification.Name("MPDClientWillDisconnect") } diff --git a/Persephone/Layouts/ArtistViewLayout.swift b/Persephone/Layouts/ArtistViewLayout.swift new file mode 100644 index 0000000..8e28f61 --- /dev/null +++ b/Persephone/Layouts/ArtistViewLayout.swift @@ -0,0 +1,22 @@ +// +// ArtistViewLayout.swift +// Persephone +// +// Created by Daniel Barber on 2019/9/29. +// Copyright © 2019 Dan Barber. All rights reserved. +// + +import AppKit + +class ArtistViewLayout: NSCollectionViewFlowLayout { + override func prepare() { + super.prepare() + + guard let collectionView = collectionView + else { return } + + let width = collectionView.bounds.size.width + + itemSize.width = width + } +} diff --git a/Persephone/MPDClient/Extensions/MPDClient+Artist.swift b/Persephone/MPDClient/Extensions/MPDClient+Artist.swift new file mode 100644 index 0000000..eef68ce --- /dev/null +++ b/Persephone/MPDClient/Extensions/MPDClient+Artist.swift @@ -0,0 +1,38 @@ +// +// MPDClient+Artist.swift +// Persephone +// +// Created by Daniel Barber on 2019/9/29. +// Copyright © 2019 Dan Barber. All rights reserved. +// + +import Foundation +import mpdclient + +extension MPDClient { + func fetchAllArtists() { + enqueueCommand(command: .fetchAllArtists) + } + + func allArtists() { + var artists: [String] = [] + + mpd_search_db_tags(self.connection, MPD_TAG_ALBUM_ARTIST) + mpd_search_commit(self.connection) + + while let pair = mpd_recv_pair(self.connection) { + let pair = MPDPair(pair) + + switch pair.name { + case "AlbumArtist": + artists.append(pair.value) + default: + break + } + + mpd_return_pair(self.connection, pair.pair) + } + + self.delegate?.didLoadArtists(mpdClient: self, artists: artists) + } +} diff --git a/Persephone/MPDClient/Extensions/MPDClient+Command.swift b/Persephone/MPDClient/Extensions/MPDClient+Command.swift index 9d96093..65845d4 100644 --- a/Persephone/MPDClient/Extensions/MPDClient+Command.swift +++ b/Persephone/MPDClient/Extensions/MPDClient+Command.swift @@ -87,6 +87,10 @@ extension MPDClient { else { return } sendAddAlbumToQueue(album: album, at: queuePos) + // Artist commands + case .fetchAllArtists: + allArtists() + // Album commands case .fetchAllAlbums: allAlbums() @@ -107,6 +111,7 @@ extension MPDClient { albumSongs(for: album, callback: callback) } + } func enqueueCommand( diff --git a/Persephone/MPDClient/Extensions/MPDClient+Connection.swift b/Persephone/MPDClient/Extensions/MPDClient+Connection.swift index 9e6175e..d219fd0 100644 --- a/Persephone/MPDClient/Extensions/MPDClient+Connection.swift +++ b/Persephone/MPDClient/Extensions/MPDClient+Connection.swift @@ -10,8 +10,8 @@ import Foundation import mpdclient extension MPDClient { - func connect(host: String, port: Int) { - commandQueue.addOperation { [unowned self] in + func makeConnectionOperation(host: String, port: Int) -> BlockOperation { + BlockOperation { [unowned self] in guard let connection = mpd_connection_new(host, UInt32(port), 10000), mpd_connection_get_error(connection) == MPD_ERROR_SUCCESS else { return } @@ -24,17 +24,17 @@ extension MPDClient { self.connection = connection self.status = MPDStatus(status) - self.fetchQueue() - self.fetchAllAlbums() - self.idle() - self.delegate?.didConnect(mpdClient: self) self.delegate?.didUpdateStatus(mpdClient: self, status: self.status!) - self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue) - self.delegate?.didUpdateQueuePos(mpdClient: self, song: self.status!.song) + + self.idle() } } + func connect() { + commandQueue.addOperation(connectionOperation) + } + func disconnect() { guard isConnected else { return } diff --git a/Persephone/MPDClient/Extensions/MPDClient+Queue.swift b/Persephone/MPDClient/Extensions/MPDClient+Queue.swift index c4f908c..4b42e88 100644 --- a/Persephone/MPDClient/Extensions/MPDClient+Queue.swift +++ b/Persephone/MPDClient/Extensions/MPDClient+Queue.swift @@ -11,7 +11,7 @@ import mpdclient extension MPDClient { func fetchQueue() { - sendCommand(command: .fetchQueue) + enqueueCommand(command: .fetchQueue) } func clearQueue() { @@ -54,6 +54,8 @@ extension MPDClient { let song = MPDSong(mpdSong) self.queue.append(song) } + + self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue) } func sendClearQueue() { diff --git a/Persephone/MPDClient/MPDClient.swift b/Persephone/MPDClient/MPDClient.swift index f705f24..75c8d36 100644 --- a/Persephone/MPDClient/MPDClient.swift +++ b/Persephone/MPDClient/MPDClient.swift @@ -10,6 +10,8 @@ import Foundation import mpdclient class MPDClient { + var connectionOperation: BlockOperation! + var delegate: MPDClientDelegate? var connection: OpaquePointer? @@ -20,8 +22,9 @@ class MPDClient { let commandQueue = OperationQueue() - init(withDelegate delegate: MPDClientDelegate?) { + init(host: String, port: Int, withDelegate delegate: MPDClientDelegate?) { commandQueue.maxConcurrentOperationCount = 1 self.delegate = delegate + self.connectionOperation = makeConnectionOperation(host: host, port: port) } } diff --git a/Persephone/MPDClient/Models/MPDCommand.swift b/Persephone/MPDClient/Models/MPDCommand.swift index 7ba12a9..1d43119 100644 --- a/Persephone/MPDClient/Models/MPDCommand.swift +++ b/Persephone/MPDClient/Models/MPDCommand.swift @@ -37,6 +37,9 @@ extension MPDClient { case addSongToQueue case addAlbumToQueue + // Artist commands + case fetchAllArtists + // Album commands case fetchAllAlbums case playAlbum diff --git a/Persephone/MPDClient/Protocols/Delegate.swift b/Persephone/MPDClient/Protocols/Delegate.swift index 576695e..3e55d58 100644 --- a/Persephone/MPDClient/Protocols/Delegate.swift +++ b/Persephone/MPDClient/Protocols/Delegate.swift @@ -21,4 +21,6 @@ protocol MPDClientDelegate { func didUpdateQueuePos(mpdClient: MPDClient, song: Int) func didLoadAlbums(mpdClient: MPDClient, albums: [MPDClient.MPDAlbum]) + + func didLoadArtists(mpdClient: MPDClient, artists: [String]) } diff --git a/Persephone/Resources/AlbumViewItem.xib b/Persephone/Resources/AlbumViewItem.xib index 8d3fc78..c69d196 100644 --- a/Persephone/Resources/AlbumViewItem.xib +++ b/Persephone/Resources/AlbumViewItem.xib @@ -1,8 +1,8 @@ - + - + @@ -59,7 +59,7 @@ - + diff --git a/Persephone/Resources/ArtistViewItem.xib b/Persephone/Resources/ArtistViewItem.xib index cdef285..7e7780b 100644 --- a/Persephone/Resources/ArtistViewItem.xib +++ b/Persephone/Resources/ArtistViewItem.xib @@ -1,8 +1,8 @@ - + - + @@ -14,12 +14,12 @@ - + - + @@ -33,5 +33,6 @@ + diff --git a/Persephone/Resources/Base.lproj/Main.storyboard b/Persephone/Resources/Base.lproj/Main.storyboard index fa32ceb..7043030 100644 --- a/Persephone/Resources/Base.lproj/Main.storyboard +++ b/Persephone/Resources/Base.lproj/Main.storyboard @@ -339,7 +339,6 @@ - @@ -882,7 +881,7 @@ - + @@ -896,9 +895,7 @@ - - - + @@ -920,6 +917,9 @@ + + + diff --git a/Persephone/State/Actions/ArtistListActions.swift b/Persephone/State/Actions/ArtistListActions.swift new file mode 100644 index 0000000..2cdeaea --- /dev/null +++ b/Persephone/State/Actions/ArtistListActions.swift @@ -0,0 +1,14 @@ +// +// ArtistListActions.swift +// Persephone +// +// Created by Daniel Barber on 2019/9/29. +// Copyright © 2019 Dan Barber. All rights reserved. +// + +import AppKit +import ReSwift + +struct UpdateArtistListAction: Action { + var artists: [String] +} diff --git a/Persephone/State/AppState.swift b/Persephone/State/AppState.swift index cec9ba0..fdc56d9 100644 --- a/Persephone/State/AppState.swift +++ b/Persephone/State/AppState.swift @@ -12,6 +12,7 @@ struct AppState: StateType { var playerState = PlayerState() var queueState = QueueState() var albumListState = AlbumListState() + var artistListState = ArtistListState() var preferencesState = PreferencesState() var uiState = UIState() } diff --git a/Persephone/State/Reducers/AppReducer.swift b/Persephone/State/Reducers/AppReducer.swift index ed0a499..f30a77a 100644 --- a/Persephone/State/Reducers/AppReducer.swift +++ b/Persephone/State/Reducers/AppReducer.swift @@ -13,6 +13,7 @@ func appReducer(action: Action, state: AppState?) -> AppState { playerState: playerReducer(action: action, state: state?.playerState), queueState: queueReducer(action: action, state: state?.queueState), albumListState: albumListReducer(action: action, state: state?.albumListState), + artistListState: artistListReducer(action: action, state: state?.artistListState), preferencesState: preferencesReducer(action: action, state: state?.preferencesState), uiState: uiReducer(action: action, state: state?.uiState) ) diff --git a/Persephone/State/Reducers/ArtistReducer.swift b/Persephone/State/Reducers/ArtistReducer.swift new file mode 100644 index 0000000..28e3fc2 --- /dev/null +++ b/Persephone/State/Reducers/ArtistReducer.swift @@ -0,0 +1,25 @@ +// +// ArtistReducer.swift +// Persephone +// +// Created by Daniel Barber on 2019/9/29. +// Copyright © 2019 Dan Barber. All rights reserved. +// + +import ReSwift + +func artistListReducer(action: Action, state: ArtistListState?) -> ArtistListState { + var state = state ?? ArtistListState() + + switch action { + case let action as UpdateArtistListAction: + state.artists = action.artists + + default: + break + + } + + return state +} + diff --git a/Persephone/Views/ArtistItemView.swift b/Persephone/Views/ArtistItemView.swift new file mode 100644 index 0000000..8bb13f9 --- /dev/null +++ b/Persephone/Views/ArtistItemView.swift @@ -0,0 +1,15 @@ +// +// ArtistViewItem.swift +// Persephone +// +// Created by Daniel Barber on 2019/9/29. +// Copyright © 2019 Dan Barber. All rights reserved. +// + +import AppKit + +class ArtistItemView: NSView { + required init?(coder decoder: NSCoder) { + super.init(coder: decoder) + } +}