From b4fe73ef7d88657581cdf43d6f64221909dd165b Mon Sep 17 00:00:00 2001 From: Dan Barber Date: Sun, 10 Feb 2019 14:52:15 -0500 Subject: [PATCH] Add album view --- Persephone.xcodeproj/project.pbxproj | 30 ++++- .../Controllers/AlbumViewController.swift | 56 ++++++++++ .../Controllers/NotificationsController.swift | 7 ++ Persephone/Extensions/Notification.swift | 2 + Persephone/MPDClient/MPDClient.swift | 104 ++++++++++++++++-- Persephone/MPDClient/Models/Album.swift | 21 ++++ Persephone/MPDClient/Models/Pair.swift | 18 +++ Persephone/MPDClient/Protocols/Delegate.swift | 1 + .../blankAlbum.imageset/Contents.json | 12 ++ .../blankAlbum.imageset/blankAlbum.pdf | Bin 0 -> 4127 bytes Persephone/Views/AlbumItem.swift | 27 +++++ Persephone/Views/AlbumItem.xib | 60 ++++++++++ .../Base.lproj/Main.storyboard | 36 +++--- Resources/export/blankAlbum.pdf | Bin 0 -> 4127 bytes Resources/icons.sketch | Bin 5646 -> 9327 bytes 15 files changed, 347 insertions(+), 27 deletions(-) create mode 100644 Persephone/Controllers/AlbumViewController.swift create mode 100644 Persephone/MPDClient/Models/Album.swift create mode 100644 Persephone/MPDClient/Models/Pair.swift create mode 100644 Persephone/Resources/Assets.xcassets/blankAlbum.imageset/Contents.json create mode 100644 Persephone/Resources/Assets.xcassets/blankAlbum.imageset/blankAlbum.pdf create mode 100644 Persephone/Views/AlbumItem.swift create mode 100644 Persephone/Views/AlbumItem.xib rename Persephone/{Resources => Views}/Base.lproj/Main.storyboard (98%) create mode 100644 Resources/export/blankAlbum.pdf diff --git a/Persephone.xcodeproj/project.pbxproj b/Persephone.xcodeproj/project.pbxproj index 00f3f0f..14200dc 100644 --- a/Persephone.xcodeproj/project.pbxproj +++ b/Persephone.xcodeproj/project.pbxproj @@ -15,6 +15,9 @@ E408D3B6220DD8970006D9BE /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3B5220DD8970006D9BE /* Notification.swift */; }; E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */; }; E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */; }; + E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3C1220E134F0006D9BE /* AlbumViewController.swift */; }; + E408D3CA220E341D0006D9BE /* AlbumItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3C8220E341D0006D9BE /* AlbumItem.swift */; }; + E408D3CB220E341D0006D9BE /* AlbumItem.xib in Resources */ = {isa = PBXBuildFile; fileRef = E408D3C9220E341D0006D9BE /* AlbumItem.xib */; }; 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 */; }; @@ -24,6 +27,8 @@ E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC912204F4B80024217A /* QueueViewController.swift */; }; E4E8CC942206097F0024217A /* NotificationsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC932206097F0024217A /* NotificationsController.swift */; }; E4E8CC9A22075D370024217A /* Song.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC9922075D370024217A /* Song.swift */; }; + E4EB2379220F10B8008C70C0 /* Pair.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4EB2378220F10B8008C70C0 /* Pair.swift */; }; + E4EB237B220F7CF1008C70C0 /* Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4EB237A220F7CF1008C70C0 /* Album.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -73,6 +78,9 @@ E408D3B5220DD8970006D9BE /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = ""; }; E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSUserInterfaceItemIdentifier.swift; sourceTree = ""; }; E408D3BD220E03EE0006D9BE /* RawRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawRepresentable.swift; sourceTree = ""; }; + E408D3C1220E134F0006D9BE /* AlbumViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumViewController.swift; sourceTree = ""; }; + E408D3C8220E341D0006D9BE /* AlbumItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumItem.swift; sourceTree = ""; }; + E408D3C9220E341D0006D9BE /* AlbumItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlbumItem.xib; sourceTree = ""; }; E41B22BF21FB6BBA00D544F6 /* libmpdclient.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libmpdclient.2.dylib; path = libmpdclient/output/libmpdclient.2.dylib; sourceTree = ""; }; E41B22C421FB715A00D544F6 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; E41B22C521FB932700D544F6 /* MPDClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDClient.swift; sourceTree = ""; }; @@ -117,6 +125,8 @@ E4E8CC912204F4B80024217A /* QueueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueViewController.swift; sourceTree = ""; }; E4E8CC932206097F0024217A /* NotificationsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsController.swift; sourceTree = ""; }; E4E8CC9922075D370024217A /* Song.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Song.swift; sourceTree = ""; }; + E4EB2378220F10B8008C70C0 /* Pair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pair.swift; sourceTree = ""; }; + E4EB237A220F7CF1008C70C0 /* Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Album.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -172,6 +182,7 @@ E408D3B7220DE8CC0006D9BE /* Extensions */, E4D1B598220BA3C90026F233 /* Resources */, E4D1B597220BA3A20026F233 /* Controllers */, + E408D3C3220E138B0006D9BE /* Views */, E41B22C721FB966C00D544F6 /* include */, E407861B2110CE6E006887B1 /* AppDelegate.swift */, E40786242110CE70006887B1 /* Info.plist */, @@ -216,6 +227,16 @@ path = Extensions; sourceTree = ""; }; + E408D3C3220E138B0006D9BE /* Views */ = { + isa = PBXGroup; + children = ( + E40786212110CE70006887B1 /* Main.storyboard */, + E408D3C8220E341D0006D9BE /* AlbumItem.swift */, + E408D3C9220E341D0006D9BE /* AlbumItem.xib */, + ); + path = Views; + sourceTree = ""; + }; E41B22BE21FB6B3300D544F6 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -291,6 +312,8 @@ children = ( E4E8CC9922075D370024217A /* Song.swift */, E4A642D922090CBE00067D21 /* Status.swift */, + E4EB2378220F10B8008C70C0 /* Pair.swift */, + E4EB237A220F7CF1008C70C0 /* Album.swift */, ); path = Models; sourceTree = ""; @@ -306,6 +329,7 @@ E4D1B597220BA3A20026F233 /* Controllers */ = { isa = PBXGroup; children = ( + E408D3C1220E134F0006D9BE /* AlbumViewController.swift */, E4E8CC932206097F0024217A /* NotificationsController.swift */, E4E8CC912204F4B80024217A /* QueueViewController.swift */, E465049921E94DF500A70F4C /* WindowController.swift */, @@ -317,7 +341,6 @@ isa = PBXGroup; children = ( E407861F2110CE70006887B1 /* Assets.xcassets */, - E40786212110CE70006887B1 /* Main.storyboard */, ); path = Resources; sourceTree = ""; @@ -433,6 +456,7 @@ buildActionMask = 2147483647; files = ( E40786202110CE70006887B1 /* Assets.xcassets in Resources */, + E408D3CB220E341D0006D9BE /* AlbumItem.xib in Resources */, E40786232110CE70006887B1 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -458,17 +482,21 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */, E4A642DA22090CBE00067D21 /* Status.swift in Sources */, E4E8CC942206097F0024217A /* NotificationsController.swift in Sources */, E408D3B6220DD8970006D9BE /* Notification.swift in Sources */, E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */, + E4EB2379220F10B8008C70C0 /* Pair.swift in Sources */, E465049A21E94DF500A70F4C /* WindowController.swift in Sources */, E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */, E407861C2110CE6E006887B1 /* AppDelegate.swift in Sources */, E4E8CC9A22075D370024217A /* Song.swift in Sources */, + E408D3CA220E341D0006D9BE /* AlbumItem.swift in Sources */, E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */, E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */, E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */, + E4EB237B220F7CF1008C70C0 /* Album.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Persephone/Controllers/AlbumViewController.swift b/Persephone/Controllers/AlbumViewController.swift new file mode 100644 index 0000000..8e67518 --- /dev/null +++ b/Persephone/Controllers/AlbumViewController.swift @@ -0,0 +1,56 @@ +// +// AlbumViewController.swift +// Persephone +// +// Created by Daniel Barber on 2019/2/08. +// Copyright © 2019 Dan Barber. All rights reserved. +// + +import Cocoa + +class AlbumViewController: NSViewController, + NSCollectionViewDataSource, + NSCollectionViewDelegate { + var albums: [MPDClient.Album] = [] + + override func viewDidLoad() { + super.viewDidLoad() + + NotificationCenter.default.addObserver( + self, + selector: #selector(updateAlbums(_:)), + name: Notification.loadedAlbums, + object: AppDelegate.mpdClient + ) + } + + @objc func updateAlbums(_ notification: Notification) { + guard let albums = notification.userInfo?[Notification.albumsKey] as? [MPDClient.Album] + else { return } + + print("Loaded albums") + self.albums = albums + + albumCollectionView.reloadData() + } + + func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int { + return albums.count + } + + func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem { + let item = collectionView.makeItem( + withIdentifier: NSUserInterfaceItemIdentifier("AlbumItem"), + for: indexPath + ) + guard let albumItem = item as? AlbumItem else { return item } + + albumItem.view.wantsLayer = true + albumItem.setAlbumTitle(albums[indexPath.item].title) + albumItem.setAlbumArtist(albums[indexPath.item].artist) + + return albumItem + } + + @IBOutlet var albumCollectionView: NSCollectionView! +} diff --git a/Persephone/Controllers/NotificationsController.swift b/Persephone/Controllers/NotificationsController.swift index 1087b0a..74a9d20 100644 --- a/Persephone/Controllers/NotificationsController.swift +++ b/Persephone/Controllers/NotificationsController.swift @@ -32,6 +32,13 @@ class NotificationsController: MPDClientDelegate { ) } + func didLoadAlbums(mpdClient: MPDClient, albums: [MPDClient.Album]) { + sendNotification( + name: Notification.loadedAlbums, + userInfo: [Notification.albumsKey: albums] + ) + } + private func sendNotification(name: Notification.Name, userInfo: [AnyHashable : Any]) { self.notificationQueue.async { NotificationCenter.default.post( diff --git a/Persephone/Extensions/Notification.swift b/Persephone/Extensions/Notification.swift index ad8e275..903f39a 100644 --- a/Persephone/Extensions/Notification.swift +++ b/Persephone/Extensions/Notification.swift @@ -12,8 +12,10 @@ extension Notification { static let stateChanged = Notification.Name("MPDClientStateChanged") static let queueChanged = Notification.Name("MPDClientQueueChanged") static let queuePosChanged = Notification.Name("MPDClientQueuePosChanged") + static let loadedAlbums = Notification.Name("MPDClientLoadedAlbums") static let stateKey = "state" static let queueKey = "queue" static let queuePosKey = "song" + static let albumsKey = "albums" } diff --git a/Persephone/MPDClient/MPDClient.swift b/Persephone/MPDClient/MPDClient.swift index 19d1c39..5a434bf 100644 --- a/Persephone/MPDClient/MPDClient.swift +++ b/Persephone/MPDClient/MPDClient.swift @@ -22,7 +22,8 @@ class MPDClient { private let commandQueue = DispatchQueue(label: "commandQueue") enum Command { - case prevTrack, nextTrack, playPause, stop, fetchStatus, fetchQueue + case prevTrack, nextTrack, playPause, stop, fetchStatus, fetchQueue, + fetchAllAlbums } struct Idle: OptionSet { @@ -57,6 +58,8 @@ class MPDClient { fetchQueue() + fetchAllAlbums() + self.delegate?.didUpdateState(mpdClient: self, state: self.status!.state) self.delegate?.didUpdateQueue(mpdClient: self, queue: self.queue) self.delegate?.didUpdateQueuePos(mpdClient: self, song: self.status!.song) @@ -78,6 +81,10 @@ class MPDClient { sendCommand(command: .fetchQueue) } + func fetchAllAlbums() { + sendCommand(command: .fetchAllAlbums) + } + func playPause() { queueCommand(command: .playPause) } @@ -103,6 +110,7 @@ class MPDClient { } func sendCommand(command: Command) { + print("Command:", command) switch command { // Transport commands @@ -114,19 +122,12 @@ class MPDClient { sendStop() case .playPause: sendPlay() - case .fetchStatus: - guard let status = mpd_run_status(connection) else { break } - self.status = Status(status) - + sendRunStatus() case .fetchQueue: - self.queue = [] - mpd_send_list_queue_meta(connection) - - while let mpdSong = mpd_recv_song(connection) { - let song = Song(mpdSong) - self.queue.append(song) - } + sendFetchQueue() + case .fetchAllAlbums: + allAlbums() } } @@ -158,6 +159,84 @@ class MPDClient { } } + func sendRunStatus() { + guard let status = mpd_run_status(connection) else { return } + self.status = Status(status) + } + + func sendFetchQueue() { + self.queue = [] + mpd_send_list_queue_meta(connection) + + while let mpdSong = mpd_recv_song(connection) { + let song = Song(mpdSong) + self.queue.append(song) + } + } + + func allAlbums() { + var albums: [Album] = [] + var artist: String = "" + + mpd_search_db_tags(self.connection, MPD_TAG_ALBUM) + mpd_search_add_group_tag(self.connection, MPD_TAG_ALBUM_ARTIST) + mpd_search_commit(self.connection) + while let mpdPair = mpd_recv_pair(self.connection) { + let name = String(cString: mpdPair.pointee.name) + let value = String(cString: mpdPair.pointee.value) + + switch name { + case "AlbumArtist": + artist = value + case "Album": + albums.append(Album(title: value, artist: artist)) + default: + break + } + + mpd_return_pair(self.connection, mpdPair) + } + + delegate?.didLoadAlbums(mpdClient: self, albums: albums) + } + + func albumsForArtist(_ artist: String) -> [Album] { + var albums: [Album] = [] + + mpd_search_db_tags(self.connection, MPD_TAG_ALBUM) + mpd_search_add_tag_constraint(self.connection, MPD_OPERATOR_DEFAULT, MPD_TAG_ARTIST, artist) + mpd_search_commit(self.connection) + while let mpdAlbumPair = mpd_recv_pair_tag(self.connection, MPD_TAG_ALBUM) { + print(artist, "-", String(cString: mpdAlbumPair.pointee.value)) + albums.append(Album(title: String(cString: mpdAlbumPair.pointee.value), artist: artist)) + mpd_return_pair(self.connection, mpdAlbumPair) + } + + return albums + } + + func allArtists() -> [String] { + var artists: [String] = [] + + mpd_search_db_tags(self.connection, MPD_TAG_ARTIST) + mpd_search_commit(self.connection) + while let mpdArtistPair = mpd_recv_pair_tag(self.connection, MPD_TAG_ARTIST) { + artists.append(String(cString: mpdArtistPair.pointee.value)) + mpd_return_pair(self.connection, mpdArtistPair) + } + + return artists + } + + func sendSearchDbTags(_ tagType: mpd_tag_type) { + mpd_search_db_tags(self.connection, tagType) + mpd_search_commit(self.connection) + while let mpdPair = mpd_recv_pair_tag(self.connection, tagType) { + print(String(cString: mpdPair.pointee.value)) + mpd_return_pair(self.connection, mpdPair) + } + } + func noIdle() { mpd_send_noidle(connection) } @@ -184,6 +263,7 @@ class MPDClient { self.delegate?.didUpdateQueuePos(mpdClient: self, song: self.status!.song) } if !mpdIdle.isEmpty { + print("Status") self.idle() } } diff --git a/Persephone/MPDClient/Models/Album.swift b/Persephone/MPDClient/Models/Album.swift new file mode 100644 index 0000000..2cb6f6c --- /dev/null +++ b/Persephone/MPDClient/Models/Album.swift @@ -0,0 +1,21 @@ +// +// Album.swift +// Persephone +// +// Created by Daniel Barber on 2019/2/09. +// Copyright © 2019 Dan Barber. All rights reserved. +// + +import Foundation + +extension MPDClient { + class Album { + let title: String + let artist: String + + init(title: String, artist: String) { + self.title = title + self.artist = artist + } + } +} diff --git a/Persephone/MPDClient/Models/Pair.swift b/Persephone/MPDClient/Models/Pair.swift new file mode 100644 index 0000000..012ba1c --- /dev/null +++ b/Persephone/MPDClient/Models/Pair.swift @@ -0,0 +1,18 @@ +// +// Pair.swift +// Persephone +// +// Created by Daniel Barber on 2019/2/09. +// Copyright © 2019 Dan Barber. All rights reserved. +// + +import Foundation +import mpdclient + +class Pair { + let mpdPair: UnsafeMutablePointer + + init(_ mpdPair: UnsafeMutablePointer) { + self.mpdPair = mpdPair + } +} diff --git a/Persephone/MPDClient/Protocols/Delegate.swift b/Persephone/MPDClient/Protocols/Delegate.swift index 1871f0e..5db155e 100644 --- a/Persephone/MPDClient/Protocols/Delegate.swift +++ b/Persephone/MPDClient/Protocols/Delegate.swift @@ -12,4 +12,5 @@ protocol MPDClientDelegate { func didUpdateState(mpdClient: MPDClient, state: MPDClient.Status.State) func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.Song]) func didUpdateQueuePos(mpdClient: MPDClient, song: Int) + func didLoadAlbums(mpdClient: MPDClient, albums: [MPDClient.Album]) } diff --git a/Persephone/Resources/Assets.xcassets/blankAlbum.imageset/Contents.json b/Persephone/Resources/Assets.xcassets/blankAlbum.imageset/Contents.json new file mode 100644 index 0000000..f6c870a --- /dev/null +++ b/Persephone/Resources/Assets.xcassets/blankAlbum.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "blankAlbum.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Persephone/Resources/Assets.xcassets/blankAlbum.imageset/blankAlbum.pdf b/Persephone/Resources/Assets.xcassets/blankAlbum.imageset/blankAlbum.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4c51e9d897f71b7c2a3e14ecf831aa552b2c5239 GIT binary patch literal 4127 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Persephone/Resources/Base.lproj/Main.storyboard b/Persephone/Views/Base.lproj/Main.storyboard similarity index 98% rename from Persephone/Resources/Base.lproj/Main.storyboard rename to Persephone/Views/Base.lproj/Main.storyboard index 634fbc1..2d1dc43 100644 --- a/Persephone/Resources/Base.lproj/Main.storyboard +++ b/Persephone/Views/Base.lproj/Main.storyboard @@ -908,47 +908,55 @@ - + - + - + - + - + - - + + + + + + + - - - - - + + + + + + + diff --git a/Resources/export/blankAlbum.pdf b/Resources/export/blankAlbum.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4c51e9d897f71b7c2a3e14ecf831aa552b2c5239 GIT binary patch literal 4127 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 literal 5646 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 b