mirror of
https://github.com/danbee/persephone
synced 2025-03-04 08:39:11 +00:00
Add album view
This commit is contained in:
parent
3cbb24f50d
commit
447432120a
@ -15,6 +15,9 @@
|
|||||||
E408D3B6220DD8970006D9BE /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3B5220DD8970006D9BE /* Notification.swift */; };
|
E408D3B6220DD8970006D9BE /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3B5220DD8970006D9BE /* Notification.swift */; };
|
||||||
E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */; };
|
E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */; };
|
||||||
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 */; };
|
||||||
|
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, ); }; };
|
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, ); }; };
|
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 */; };
|
||||||
@ -24,6 +27,8 @@
|
|||||||
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC912204F4B80024217A /* QueueViewController.swift */; };
|
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC912204F4B80024217A /* QueueViewController.swift */; };
|
||||||
E4E8CC942206097F0024217A /* NotificationsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC932206097F0024217A /* NotificationsController.swift */; };
|
E4E8CC942206097F0024217A /* NotificationsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC932206097F0024217A /* NotificationsController.swift */; };
|
||||||
E4E8CC9A22075D370024217A /* Song.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4E8CC9922075D370024217A /* Song.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 */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -73,6 +78,9 @@
|
|||||||
E408D3B5220DD8970006D9BE /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = "<group>"; };
|
E408D3B5220DD8970006D9BE /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = "<group>"; };
|
||||||
E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSUserInterfaceItemIdentifier.swift; sourceTree = "<group>"; };
|
E408D3B8220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSUserInterfaceItemIdentifier.swift; sourceTree = "<group>"; };
|
||||||
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>"; };
|
||||||
|
E408D3C8220E341D0006D9BE /* AlbumItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumItem.swift; sourceTree = "<group>"; };
|
||||||
|
E408D3C9220E341D0006D9BE /* AlbumItem.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AlbumItem.xib; 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>"; };
|
||||||
E41B22C421FB715A00D544F6 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
|
E41B22C421FB715A00D544F6 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = "<group>"; };
|
||||||
E41B22C521FB932700D544F6 /* MPDClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDClient.swift; sourceTree = "<group>"; };
|
E41B22C521FB932700D544F6 /* MPDClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDClient.swift; sourceTree = "<group>"; };
|
||||||
@ -117,6 +125,8 @@
|
|||||||
E4E8CC912204F4B80024217A /* QueueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueViewController.swift; sourceTree = "<group>"; };
|
E4E8CC912204F4B80024217A /* QueueViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueueViewController.swift; sourceTree = "<group>"; };
|
||||||
E4E8CC932206097F0024217A /* NotificationsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsController.swift; sourceTree = "<group>"; };
|
E4E8CC932206097F0024217A /* NotificationsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsController.swift; sourceTree = "<group>"; };
|
||||||
E4E8CC9922075D370024217A /* Song.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Song.swift; sourceTree = "<group>"; };
|
E4E8CC9922075D370024217A /* Song.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Song.swift; sourceTree = "<group>"; };
|
||||||
|
E4EB2378220F10B8008C70C0 /* Pair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pair.swift; sourceTree = "<group>"; };
|
||||||
|
E4EB237A220F7CF1008C70C0 /* Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Album.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -172,6 +182,7 @@
|
|||||||
E408D3B7220DE8CC0006D9BE /* Extensions */,
|
E408D3B7220DE8CC0006D9BE /* Extensions */,
|
||||||
E4D1B598220BA3C90026F233 /* Resources */,
|
E4D1B598220BA3C90026F233 /* Resources */,
|
||||||
E4D1B597220BA3A20026F233 /* Controllers */,
|
E4D1B597220BA3A20026F233 /* Controllers */,
|
||||||
|
E408D3C3220E138B0006D9BE /* Views */,
|
||||||
E41B22C721FB966C00D544F6 /* include */,
|
E41B22C721FB966C00D544F6 /* include */,
|
||||||
E407861B2110CE6E006887B1 /* AppDelegate.swift */,
|
E407861B2110CE6E006887B1 /* AppDelegate.swift */,
|
||||||
E40786242110CE70006887B1 /* Info.plist */,
|
E40786242110CE70006887B1 /* Info.plist */,
|
||||||
@ -216,6 +227,16 @@
|
|||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
E408D3C3220E138B0006D9BE /* Views */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E40786212110CE70006887B1 /* Main.storyboard */,
|
||||||
|
E408D3C8220E341D0006D9BE /* AlbumItem.swift */,
|
||||||
|
E408D3C9220E341D0006D9BE /* AlbumItem.xib */,
|
||||||
|
);
|
||||||
|
path = Views;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
E41B22BE21FB6B3300D544F6 /* Frameworks */ = {
|
E41B22BE21FB6B3300D544F6 /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -291,6 +312,8 @@
|
|||||||
children = (
|
children = (
|
||||||
E4E8CC9922075D370024217A /* Song.swift */,
|
E4E8CC9922075D370024217A /* Song.swift */,
|
||||||
E4A642D922090CBE00067D21 /* Status.swift */,
|
E4A642D922090CBE00067D21 /* Status.swift */,
|
||||||
|
E4EB2378220F10B8008C70C0 /* Pair.swift */,
|
||||||
|
E4EB237A220F7CF1008C70C0 /* Album.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -306,6 +329,7 @@
|
|||||||
E4D1B597220BA3A20026F233 /* Controllers */ = {
|
E4D1B597220BA3A20026F233 /* Controllers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E408D3C1220E134F0006D9BE /* AlbumViewController.swift */,
|
||||||
E4E8CC932206097F0024217A /* NotificationsController.swift */,
|
E4E8CC932206097F0024217A /* NotificationsController.swift */,
|
||||||
E4E8CC912204F4B80024217A /* QueueViewController.swift */,
|
E4E8CC912204F4B80024217A /* QueueViewController.swift */,
|
||||||
E465049921E94DF500A70F4C /* WindowController.swift */,
|
E465049921E94DF500A70F4C /* WindowController.swift */,
|
||||||
@ -317,7 +341,6 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E407861F2110CE70006887B1 /* Assets.xcassets */,
|
E407861F2110CE70006887B1 /* Assets.xcassets */,
|
||||||
E40786212110CE70006887B1 /* Main.storyboard */,
|
|
||||||
);
|
);
|
||||||
path = Resources;
|
path = Resources;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -433,6 +456,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
E40786202110CE70006887B1 /* Assets.xcassets in Resources */,
|
E40786202110CE70006887B1 /* Assets.xcassets in Resources */,
|
||||||
|
E408D3CB220E341D0006D9BE /* AlbumItem.xib in Resources */,
|
||||||
E40786232110CE70006887B1 /* Main.storyboard in Resources */,
|
E40786232110CE70006887B1 /* Main.storyboard in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@ -458,17 +482,21 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
E408D3C2220E134F0006D9BE /* AlbumViewController.swift in Sources */,
|
||||||
E4A642DA22090CBE00067D21 /* Status.swift in Sources */,
|
E4A642DA22090CBE00067D21 /* Status.swift in Sources */,
|
||||||
E4E8CC942206097F0024217A /* NotificationsController.swift in Sources */,
|
E4E8CC942206097F0024217A /* NotificationsController.swift in Sources */,
|
||||||
E408D3B6220DD8970006D9BE /* Notification.swift in Sources */,
|
E408D3B6220DD8970006D9BE /* Notification.swift in Sources */,
|
||||||
E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */,
|
E408D3B9220DE98F0006D9BE /* NSUserInterfaceItemIdentifier.swift in Sources */,
|
||||||
|
E4EB2379220F10B8008C70C0 /* Pair.swift in Sources */,
|
||||||
E465049A21E94DF500A70F4C /* WindowController.swift in Sources */,
|
E465049A21E94DF500A70F4C /* WindowController.swift in Sources */,
|
||||||
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */,
|
E41B22C621FB932700D544F6 /* MPDClient.swift in Sources */,
|
||||||
E407861C2110CE6E006887B1 /* AppDelegate.swift in Sources */,
|
E407861C2110CE6E006887B1 /* AppDelegate.swift in Sources */,
|
||||||
E4E8CC9A22075D370024217A /* Song.swift in Sources */,
|
E4E8CC9A22075D370024217A /* Song.swift in Sources */,
|
||||||
|
E408D3CA220E341D0006D9BE /* AlbumItem.swift in Sources */,
|
||||||
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */,
|
E4E8CC902204EC7F0024217A /* Delegate.swift in Sources */,
|
||||||
E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */,
|
E408D3BE220E03EE0006D9BE /* RawRepresentable.swift in Sources */,
|
||||||
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */,
|
E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */,
|
||||||
|
E4EB237B220F7CF1008C70C0 /* Album.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
56
Persephone/Controllers/AlbumViewController.swift
Normal file
56
Persephone/Controllers/AlbumViewController.swift
Normal file
@ -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!
|
||||||
|
}
|
||||||
@ -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]) {
|
private func sendNotification(name: Notification.Name, userInfo: [AnyHashable : Any]) {
|
||||||
self.notificationQueue.async {
|
self.notificationQueue.async {
|
||||||
NotificationCenter.default.post(
|
NotificationCenter.default.post(
|
||||||
|
|||||||
@ -12,8 +12,10 @@ extension Notification {
|
|||||||
static let stateChanged = Notification.Name("MPDClientStateChanged")
|
static let stateChanged = Notification.Name("MPDClientStateChanged")
|
||||||
static let queueChanged = Notification.Name("MPDClientQueueChanged")
|
static let queueChanged = Notification.Name("MPDClientQueueChanged")
|
||||||
static let queuePosChanged = Notification.Name("MPDClientQueuePosChanged")
|
static let queuePosChanged = Notification.Name("MPDClientQueuePosChanged")
|
||||||
|
static let loadedAlbums = Notification.Name("MPDClientLoadedAlbums")
|
||||||
|
|
||||||
static let stateKey = "state"
|
static let stateKey = "state"
|
||||||
static let queueKey = "queue"
|
static let queueKey = "queue"
|
||||||
static let queuePosKey = "song"
|
static let queuePosKey = "song"
|
||||||
|
static let albumsKey = "albums"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,8 @@ class MPDClient {
|
|||||||
private let commandQueue = DispatchQueue(label: "commandQueue")
|
private let commandQueue = DispatchQueue(label: "commandQueue")
|
||||||
|
|
||||||
enum Command {
|
enum Command {
|
||||||
case prevTrack, nextTrack, playPause, stop, fetchStatus, fetchQueue
|
case prevTrack, nextTrack, playPause, stop, fetchStatus, fetchQueue,
|
||||||
|
fetchAllAlbums
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Idle: OptionSet {
|
struct Idle: OptionSet {
|
||||||
@ -57,6 +58,8 @@ class MPDClient {
|
|||||||
|
|
||||||
fetchQueue()
|
fetchQueue()
|
||||||
|
|
||||||
|
fetchAllAlbums()
|
||||||
|
|
||||||
self.delegate?.didUpdateState(mpdClient: self, state: self.status!.state)
|
self.delegate?.didUpdateState(mpdClient: self, state: self.status!.state)
|
||||||
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)
|
||||||
@ -78,6 +81,10 @@ class MPDClient {
|
|||||||
sendCommand(command: .fetchQueue)
|
sendCommand(command: .fetchQueue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fetchAllAlbums() {
|
||||||
|
sendCommand(command: .fetchAllAlbums)
|
||||||
|
}
|
||||||
|
|
||||||
func playPause() {
|
func playPause() {
|
||||||
queueCommand(command: .playPause)
|
queueCommand(command: .playPause)
|
||||||
}
|
}
|
||||||
@ -103,6 +110,7 @@ class MPDClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sendCommand(command: Command) {
|
func sendCommand(command: Command) {
|
||||||
|
print("Command:", command)
|
||||||
switch command {
|
switch command {
|
||||||
|
|
||||||
// Transport commands
|
// Transport commands
|
||||||
@ -114,19 +122,12 @@ class MPDClient {
|
|||||||
sendStop()
|
sendStop()
|
||||||
case .playPause:
|
case .playPause:
|
||||||
sendPlay()
|
sendPlay()
|
||||||
|
|
||||||
case .fetchStatus:
|
case .fetchStatus:
|
||||||
guard let status = mpd_run_status(connection) else { break }
|
sendRunStatus()
|
||||||
self.status = Status(status)
|
|
||||||
|
|
||||||
case .fetchQueue:
|
case .fetchQueue:
|
||||||
self.queue = []
|
sendFetchQueue()
|
||||||
mpd_send_list_queue_meta(connection)
|
case .fetchAllAlbums:
|
||||||
|
allAlbums()
|
||||||
while let mpdSong = mpd_recv_song(connection) {
|
|
||||||
let song = Song(mpdSong)
|
|
||||||
self.queue.append(song)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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() {
|
func noIdle() {
|
||||||
mpd_send_noidle(connection)
|
mpd_send_noidle(connection)
|
||||||
}
|
}
|
||||||
@ -184,6 +263,7 @@ class MPDClient {
|
|||||||
self.delegate?.didUpdateQueuePos(mpdClient: self, song: self.status!.song)
|
self.delegate?.didUpdateQueuePos(mpdClient: self, song: self.status!.song)
|
||||||
}
|
}
|
||||||
if !mpdIdle.isEmpty {
|
if !mpdIdle.isEmpty {
|
||||||
|
print("Status")
|
||||||
self.idle()
|
self.idle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
21
Persephone/MPDClient/Models/Album.swift
Normal file
21
Persephone/MPDClient/Models/Album.swift
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
Persephone/MPDClient/Models/Pair.swift
Normal file
18
Persephone/MPDClient/Models/Pair.swift
Normal file
@ -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<mpd_pair>
|
||||||
|
|
||||||
|
init(_ mpdPair: UnsafeMutablePointer<mpd_pair>) {
|
||||||
|
self.mpdPair = mpdPair
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,4 +12,5 @@ protocol MPDClientDelegate {
|
|||||||
func didUpdateState(mpdClient: MPDClient, state: MPDClient.Status.State)
|
func didUpdateState(mpdClient: MPDClient, state: MPDClient.Status.State)
|
||||||
func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.Song])
|
func didUpdateQueue(mpdClient: MPDClient, queue: [MPDClient.Song])
|
||||||
func didUpdateQueuePos(mpdClient: MPDClient, song: Int)
|
func didUpdateQueuePos(mpdClient: MPDClient, song: Int)
|
||||||
|
func didLoadAlbums(mpdClient: MPDClient, albums: [MPDClient.Album])
|
||||||
}
|
}
|
||||||
|
|||||||
12
Persephone/Resources/Assets.xcassets/blankAlbum.imageset/Contents.json
vendored
Normal file
12
Persephone/Resources/Assets.xcassets/blankAlbum.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "blankAlbum.pdf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Persephone/Resources/Assets.xcassets/blankAlbum.imageset/blankAlbum.pdf
vendored
Normal file
BIN
Persephone/Resources/Assets.xcassets/blankAlbum.imageset/blankAlbum.pdf
vendored
Normal file
Binary file not shown.
27
Persephone/Views/AlbumItem.swift
Normal file
27
Persephone/Views/AlbumItem.swift
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// AlbumItem.swift
|
||||||
|
// Persephone
|
||||||
|
//
|
||||||
|
// Created by Daniel Barber on 2019/2/08.
|
||||||
|
// Copyright © 2019 Dan Barber. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
class AlbumItem: NSCollectionViewItem {
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
// Do view setup here.
|
||||||
|
}
|
||||||
|
|
||||||
|
func setAlbumTitle(_ title: String) {
|
||||||
|
albumTitle.stringValue = title
|
||||||
|
}
|
||||||
|
|
||||||
|
func setAlbumArtist(_ artist: String) {
|
||||||
|
albumArtist.stringValue = artist
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBOutlet var albumTitle: NSTextField!
|
||||||
|
@IBOutlet var albumArtist: NSTextField!
|
||||||
|
}
|
||||||
60
Persephone/Views/AlbumItem.xib
Normal file
60
Persephone/Views/AlbumItem.xib
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="macosx"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<objects>
|
||||||
|
<customObject id="-2" userLabel="File's Owner" customClass="AlbumItem" customModule="Persephone" customModuleProvider="target">
|
||||||
|
<connections>
|
||||||
|
<outlet property="albumArtist" destination="5Uu-j1-qyT" id="2Et-tX-InT"/>
|
||||||
|
<outlet property="albumTitle" destination="KEh-NL-c2W" id="SI3-hm-H2B"/>
|
||||||
|
<outlet property="view" destination="Hz6-mo-xeY" id="v7W-XA-Emc"/>
|
||||||
|
</connections>
|
||||||
|
</customObject>
|
||||||
|
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||||
|
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||||
|
<customView id="Hz6-mo-xeY">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="206" height="248"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<textField identifier="albumTitle" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEh-NL-c2W">
|
||||||
|
<rect key="frame" x="-2" y="20" width="37" height="17"/>
|
||||||
|
<textFieldCell key="cell" lineBreakMode="clipping" title="Label" id="pDs-0t-e1j">
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
<textField identifier="albumArtist" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5Uu-j1-qyT">
|
||||||
|
<rect key="frame" x="-2" y="0.0" width="37" height="17"/>
|
||||||
|
<textFieldCell key="cell" lineBreakMode="clipping" title="Label" id="yZn-e9-zyP">
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
<imageView identifier="albumArtwork" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Kfb-8f-ean">
|
||||||
|
<rect key="frame" x="0.0" y="42" width="206" height="206"/>
|
||||||
|
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="blankAlbum" id="FsA-JX-BFh"/>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="KEh-NL-c2W" firstAttribute="leading" secondItem="5Uu-j1-qyT" secondAttribute="leading" id="Iji-NN-ju3"/>
|
||||||
|
<constraint firstItem="Kfb-8f-ean" firstAttribute="leading" secondItem="Hz6-mo-xeY" secondAttribute="leading" id="JMi-4i-dgs"/>
|
||||||
|
<constraint firstItem="KEh-NL-c2W" firstAttribute="top" secondItem="Kfb-8f-ean" secondAttribute="bottom" constant="5" id="Jdb-sY-lnB"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="Kfb-8f-ean" secondAttribute="trailing" id="KQC-Wz-Bsg"/>
|
||||||
|
<constraint firstItem="Kfb-8f-ean" firstAttribute="top" secondItem="Hz6-mo-xeY" secondAttribute="top" id="Qbk-jx-zAi"/>
|
||||||
|
<constraint firstItem="Kfb-8f-ean" firstAttribute="leading" secondItem="KEh-NL-c2W" secondAttribute="leading" id="diY-bi-0Yg"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="5Uu-j1-qyT" secondAttribute="bottom" id="puG-ym-5hh"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="KEh-NL-c2W" secondAttribute="bottom" constant="20" symbolic="YES" id="yIJ-L1-2c5"/>
|
||||||
|
</constraints>
|
||||||
|
<point key="canvasLocation" x="3" y="142"/>
|
||||||
|
</customView>
|
||||||
|
<collectionViewItem id="Qgu-aI-55A" customClass="AlbumItem" customModule="Persephone" customModuleProvider="target"/>
|
||||||
|
</objects>
|
||||||
|
<resources>
|
||||||
|
<image name="blankAlbum" width="128" height="128"/>
|
||||||
|
</resources>
|
||||||
|
</document>
|
||||||
@ -908,47 +908,55 @@
|
|||||||
</objects>
|
</objects>
|
||||||
<point key="canvasLocation" x="744" y="459"/>
|
<point key="canvasLocation" x="744" y="459"/>
|
||||||
</scene>
|
</scene>
|
||||||
<!--View Controller-->
|
<!--Album View Controller-->
|
||||||
<scene sceneID="7Ua-Hj-zWt">
|
<scene sceneID="7Ua-Hj-zWt">
|
||||||
<objects>
|
<objects>
|
||||||
<viewController id="gPn-fP-LFc" sceneMemberID="viewController">
|
<viewController id="gPn-fP-LFc" customClass="AlbumViewController" customModule="Persephone" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<view key="view" id="v1d-6d-Fl3">
|
<view key="view" id="v1d-6d-Fl3">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<scrollView wantsLayer="YES" borderType="none" autohidesScrollers="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wia-Qk-FEw">
|
<scrollView wantsLayer="YES" borderType="none" autohidesScrollers="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="i5f-35-7x8">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
||||||
<clipView key="contentView" id="leP-Jn-VMY">
|
<clipView key="contentView" id="iXE-Xl-zu6">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
<rect key="frame" x="0.0" y="0.0" width="450" height="300"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<collectionView id="2XB-gk-sPO">
|
<collectionView id="lfq-AB-epE">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="450" height="158"/>
|
<rect key="frame" x="0.0" y="0.0" width="450" height="158"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES"/>
|
||||||
<collectionViewFlowLayout key="collectionViewLayout" minimumInteritemSpacing="10" minimumLineSpacing="10" id="OKj-Q2-LS1">
|
<collectionViewFlowLayout key="collectionViewLayout" minimumInteritemSpacing="20" minimumLineSpacing="20" id="K0l-P8-OW0">
|
||||||
<size key="itemSize" width="50" height="50"/>
|
<size key="itemSize" width="206" height="248"/>
|
||||||
|
<edgeInsets key="sectionInset" left="40" right="40" top="20" bottom="60"/>
|
||||||
</collectionViewFlowLayout>
|
</collectionViewFlowLayout>
|
||||||
<color key="primaryBackgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
<color key="primaryBackgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<connections>
|
||||||
|
<outlet property="dataSource" destination="gPn-fP-LFc" id="2VB-5V-ltv"/>
|
||||||
|
<outlet property="delegate" destination="gPn-fP-LFc" id="LQ2-Vl-r08"/>
|
||||||
|
</connections>
|
||||||
</collectionView>
|
</collectionView>
|
||||||
</subviews>
|
</subviews>
|
||||||
</clipView>
|
</clipView>
|
||||||
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="Dqn-NC-UFS">
|
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="bK3-Ei-Odz">
|
||||||
<rect key="frame" x="1" y="144" width="233" height="15"/>
|
<rect key="frame" x="1" y="213" width="348" height="16"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</scroller>
|
</scroller>
|
||||||
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="Enn-qw-4lt">
|
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="SkF-fO-hfN">
|
||||||
<rect key="frame" x="234" y="1" width="15" height="143"/>
|
<rect key="frame" x="234" y="1" width="15" height="143"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</scroller>
|
</scroller>
|
||||||
</scrollView>
|
</scrollView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="wia-Qk-FEw" firstAttribute="top" secondItem="v1d-6d-Fl3" secondAttribute="top" id="NG4-cV-AAv"/>
|
<constraint firstItem="i5f-35-7x8" firstAttribute="top" secondItem="v1d-6d-Fl3" secondAttribute="top" id="BXd-LQ-OWX"/>
|
||||||
<constraint firstAttribute="bottom" secondItem="wia-Qk-FEw" secondAttribute="bottom" id="emh-fE-n6N"/>
|
<constraint firstItem="i5f-35-7x8" firstAttribute="leading" secondItem="v1d-6d-Fl3" secondAttribute="leading" id="Bgm-NV-0Ft"/>
|
||||||
<constraint firstAttribute="trailing" secondItem="wia-Qk-FEw" secondAttribute="trailing" id="izX-0D-aEA"/>
|
<constraint firstAttribute="bottom" secondItem="i5f-35-7x8" secondAttribute="bottom" id="G8K-PG-9Zq"/>
|
||||||
<constraint firstItem="wia-Qk-FEw" firstAttribute="leading" secondItem="v1d-6d-Fl3" secondAttribute="leading" id="v7v-S1-h5Z"/>
|
<constraint firstAttribute="trailing" secondItem="i5f-35-7x8" secondAttribute="trailing" id="vcm-wb-Ghi"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
|
<connections>
|
||||||
|
<outlet property="albumCollectionView" destination="lfq-AB-epE" id="p69-Fs-hCN"/>
|
||||||
|
</connections>
|
||||||
</viewController>
|
</viewController>
|
||||||
<customObject id="uex-Ws-5X4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
<customObject id="uex-Ws-5X4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
BIN
Resources/export/blankAlbum.pdf
Normal file
BIN
Resources/export/blankAlbum.pdf
Normal file
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user