diff --git a/Persephone.xcodeproj/project.pbxproj b/Persephone.xcodeproj/project.pbxproj index cf6d187..cd977d9 100644 --- a/Persephone.xcodeproj/project.pbxproj +++ b/Persephone.xcodeproj/project.pbxproj @@ -56,6 +56,7 @@ E450AD9522262DF10091BED3 /* CoverArtQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = E450AD9422262DF10091BED3 /* CoverArtQueue.swift */; }; E450AD98222633920091BED3 /* Alamofire.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD96222633920091BED3 /* Alamofire.framework.dSYM */; }; E450ADA12229E7C90091BED3 /* PMKFoundation.framework.dSYM in Resources */ = {isa = PBXBuildFile; fileRef = E450AD9F2229E7C90091BED3 /* PMKFoundation.framework.dSYM */; }; + E45878382296173C00586A1C /* AlbumDetailSongRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45878372296173C00586A1C /* AlbumDetailSongRowView.swift */; }; E45962C62241A78500FC1A1E /* MPDCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45962C52241A78500FC1A1E /* MPDCommand.swift */; }; E45E4FDA22515D87004B537F /* CHANGELOG.md in Resources */ = {isa = PBXBuildFile; fileRef = E45E4FD722515D87004B537F /* CHANGELOG.md */; }; E45E4FDB22515D87004B537F /* Brewfile in Resources */ = {isa = PBXBuildFile; fileRef = E45E4FD822515D87004B537F /* Brewfile */; }; @@ -70,6 +71,7 @@ E47E2FDD2220A6D100F747E6 /* Time.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FDC2220A6D100F747E6 /* Time.swift */; }; E47E2FE52220AA0700F747E6 /* AlbumViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = E47E2FE42220AA0700F747E6 /* AlbumViewLayout.swift */; }; E4928E0B2218D62A001D4BEA /* CGColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4928E0A2218D62A001D4BEA /* CGColor.swift */; }; + E4A3A6A122A457B600EA2C40 /* AlbumDetailSongListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A3A6A022A457B600EA2C40 /* AlbumDetailSongListView.swift */; }; E4A642DA22090CBE00067D21 /* MPDStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A642D922090CBE00067D21 /* MPDStatus.swift */; }; E4A83BEF2221F8CF0098FED6 /* CoverArtPrefsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */; }; E4A83BF12221FAA00098FED6 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */; }; @@ -270,6 +272,7 @@ E450AD9E2229B9BC0091BED3 /* PersephoneBridgingHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PersephoneBridgingHeader.h; sourceTree = ""; }; E450AD9F2229E7C90091BED3 /* PMKFoundation.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = PMKFoundation.framework.dSYM; path = Carthage/Build/Mac/PMKFoundation.framework.dSYM; sourceTree = ""; }; E450ADA02229E7C90091BED3 /* PMKFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PMKFoundation.framework; path = Carthage/Build/Mac/PMKFoundation.framework; sourceTree = ""; }; + E45878372296173C00586A1C /* AlbumDetailSongRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDetailSongRowView.swift; sourceTree = ""; }; E45962C52241A78500FC1A1E /* MPDCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDCommand.swift; sourceTree = ""; }; E45E4FD722515D87004B537F /* CHANGELOG.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = SOURCE_ROOT; }; E45E4FD822515D87004B537F /* Brewfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Brewfile; sourceTree = SOURCE_ROOT; }; @@ -285,6 +288,7 @@ E47E2FDC2220A6D100F747E6 /* Time.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Time.swift; sourceTree = ""; }; E47E2FE42220AA0700F747E6 /* AlbumViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlbumViewLayout.swift; sourceTree = ""; }; E4928E0A2218D62A001D4BEA /* CGColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGColor.swift; sourceTree = ""; }; + E4A3A6A022A457B600EA2C40 /* AlbumDetailSongListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumDetailSongListView.swift; sourceTree = ""; }; E4A642D922090CBE00067D21 /* MPDStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MPDStatus.swift; sourceTree = ""; }; E4A83BEE2221F8CF0098FED6 /* CoverArtPrefsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoverArtPrefsController.swift; sourceTree = ""; }; E4A83BF02221FAA00098FED6 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = ""; }; @@ -464,6 +468,8 @@ E47E2FD222205D2500F747E6 /* MainWindow.swift */, E4B11BB7227538FA0075461B /* CurrentCoverArtView.swift */, E423563F228623D2001216D6 /* QueueSongTitleView.swift */, + E45878372296173C00586A1C /* AlbumDetailSongRowView.swift */, + E4A3A6A022A457B600EA2C40 /* AlbumDetailSongListView.swift */, ); path = Views; sourceTree = ""; @@ -970,10 +976,12 @@ E41E530E223EF4CF00173814 /* CoverArtService+Caching.swift in Sources */, E4E8CC922204F4B80024217A /* QueueViewController.swift in Sources */, E440519C227BAF2E0090CD6F /* UIActions.swift in Sources */, + E45878382296173C00586A1C /* AlbumDetailSongRowView.swift in Sources */, E41E5312223EF74A00173814 /* CoverArtService+Filesystem.swift in Sources */, E41E5301223BF99300173814 /* MPDClient+Queue.swift in Sources */, E4EB237B220F7CF1008C70C0 /* MPDAlbum.swift in Sources */, E41E5303223BF9C300173814 /* MPDClient+Idle.swift in Sources */, + E4A3A6A122A457B600EA2C40 /* AlbumDetailSongListView.swift in Sources */, E4B11B53226928F20075461B /* AppState.swift in Sources */, E435E3E4221CD75D00184CFC /* NSImage.swift in Sources */, E440519A22787CF60090CD6F /* MPDReducer.swift in Sources */, diff --git a/Persephone/Controllers/AlbumDetailView.swift b/Persephone/Controllers/AlbumDetailView.swift index f668e18..a4639cd 100644 --- a/Persephone/Controllers/AlbumDetailView.swift +++ b/Persephone/Controllers/AlbumDetailView.swift @@ -22,8 +22,8 @@ class AlbumDetailView: NSViewController { albumTracksView.dataSource = dataSource albumTracksView.delegate = self - albumTracksView.intercellSpacing = CGSize(width: 0, height: 13) - albumTracksView.gridStyleMask = .solidHorizontalGridLineMask + albumTracksView.intercellSpacing = CGSize(width: 0, height: 18) + albumTracksView.floatsGroupRows = false albumCoverView.wantsLayer = true albumCoverView.layer?.cornerRadius = 5 @@ -47,9 +47,11 @@ class AlbumDetailView: NSViewController { func getAlbumSongs(for album: Album) { App.mpdClient.getAlbumSongs(for: album.mpdAlbum) { (mpdSongs: [MPDClient.MPDSong]) in - self.dataSource.albumTracks = mpdSongs.map { - return Song(mpdSong: $0) - } + self.dataSource.setAlbumSongs( + mpdSongs.map { Song(mpdSong: $0) } + ) + + self.getBigCoverArt(song: self.dataSource.albumSongs.first!.song ?? self.dataSource.albumSongs[1].song!) DispatchQueue.main.async { self.albumTracksView.reloadData() @@ -57,6 +59,20 @@ class AlbumDetailView: NSViewController { } } + func getBigCoverArt(song: Song) { + let coverArtService = CoverArtService(song: song) + + coverArtService.fetchBigCoverArt() + .done() { image in + DispatchQueue.main.async { + if let image = image { + self.albumCoverView.image = image + } + } + } + .cauterize() + } + func setAppearance() { if #available(OSX 10.14, *) { let darkMode = NSApp.effectiveAppearance.bestMatch(from: @@ -75,26 +91,45 @@ class AlbumDetailView: NSViewController { extension AlbumDetailView: NSTableViewDelegate { func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { - let song = dataSource.albumTracks[row] - - switch tableColumn?.identifier.rawValue { - case "trackNumberColumn": - return cellForTrackNumber(tableView, with: song) - case "trackTitleColumn": - return cellForSongTitle(tableView, with: song) - case "trackDurationColumn": - return cellForSongDuration(tableView, with: song) - default: - return nil + if let song = dataSource.albumSongs[row].song { + switch tableColumn?.identifier.rawValue { + case "trackNumberColumn": + return cellForTrackNumber(tableView, with: song) + case "trackTitleColumn": + return cellForSongTitle(tableView, with: song) + case "trackDurationColumn": + return cellForSongDuration(tableView, with: song) + default: + return nil + } + } else if let disc = dataSource.albumSongs[row].disc { + return cellForDiscNumber(tableView, with: disc) } + + return nil + } + + func tableView(_ tableView: NSTableView, isGroupRow row: Int) -> Bool { + return dataSource.albumSongs[row].disc != nil } func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? { - let view = NSTableRowView() + let view = AlbumDetailSongRowView() return view } + func cellForDiscNumber(_ tableView: NSTableView, with disc: String) -> NSView { + let cellView = tableView.makeView( + withIdentifier: .discNumber, + owner: self + ) as! NSTableCellView + + cellView.textField?.stringValue = "Disc \(disc)" + + return cellView + } + func cellForTrackNumber(_ tableView: NSTableView, with song: Song) -> NSView { let cellView = tableView.makeView( withIdentifier: .trackNumber, diff --git a/Persephone/DataSources/AlbumTracksDataSource.swift b/Persephone/DataSources/AlbumTracksDataSource.swift index 678dfe6..39834c5 100644 --- a/Persephone/DataSources/AlbumTracksDataSource.swift +++ b/Persephone/DataSources/AlbumTracksDataSource.swift @@ -9,9 +9,37 @@ import AppKit class AlbumTracksDataSource: NSObject, NSTableViewDataSource { - var albumTracks: [Song] = [] + struct AlbumSongItem { + let disc: String? + let song: Song? + + init(song: Song) { + self.disc = nil + self.song = song + } + + init(disc: String) { + self.disc = disc + self.song = nil + } + } + + var albumSongs: [AlbumSongItem] = [] + + func setAlbumSongs(_ songs: [Song]) { + var disc: String? = "" + + songs.forEach { song in + if song.disc != disc { + disc = song.disc + albumSongs.append(AlbumSongItem(disc: song.disc)) + } + + albumSongs.append(AlbumSongItem(song: song)) + } + } func numberOfRows(in tableView: NSTableView) -> Int { - return albumTracks.count + return albumSongs.count } } diff --git a/Persephone/Extensions/NSUserInterfaceItemIdentifier.swift b/Persephone/Extensions/NSUserInterfaceItemIdentifier.swift index aba4675..e1479be 100644 --- a/Persephone/Extensions/NSUserInterfaceItemIdentifier.swift +++ b/Persephone/Extensions/NSUserInterfaceItemIdentifier.swift @@ -18,6 +18,7 @@ extension NSUserInterfaceItemIdentifier { static let albumViewItem = NSUserInterfaceItemIdentifier("AlbumViewItem") + static let discNumber = NSUserInterfaceItemIdentifier("discNumberCell") static let trackNumber = NSUserInterfaceItemIdentifier("trackNumberCell") static let songTitle = NSUserInterfaceItemIdentifier("songTitleCell") static let songDuration = NSUserInterfaceItemIdentifier("songDurationCell") diff --git a/Persephone/Models/Song.swift b/Persephone/Models/Song.swift index 6e6a5f2..5b0a2a8 100644 --- a/Persephone/Models/Song.swift +++ b/Persephone/Models/Song.swift @@ -11,6 +11,10 @@ import Foundation struct Song { var mpdSong: MPDClient.MPDSong + var disc: String { + return mpdSong.getTag(.disc) + } + var trackNumber: String { return mpdSong.getTag(.track) } diff --git a/Persephone/Resources/AlbumDetailView.xib b/Persephone/Resources/AlbumDetailView.xib index 2efb6b0..0b3f423 100644 --- a/Persephone/Resources/AlbumDetailView.xib +++ b/Persephone/Resources/AlbumDetailView.xib @@ -22,9 +22,9 @@ - + - + @@ -33,7 +33,7 @@ - + @@ -41,29 +41,33 @@ - + - - + + + - - + - + - - + + @@ -104,6 +108,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -196,17 +224,20 @@ - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/Persephone/Resources/Base.lproj/Main.storyboard b/Persephone/Resources/Base.lproj/Main.storyboard index c657154..87d1b04 100644 --- a/Persephone/Resources/Base.lproj/Main.storyboard +++ b/Persephone/Resources/Base.lproj/Main.storyboard @@ -569,8 +569,8 @@ - - + + diff --git a/Persephone/Views/AlbumDetailSongListView.swift b/Persephone/Views/AlbumDetailSongListView.swift new file mode 100644 index 0000000..9fe6f83 --- /dev/null +++ b/Persephone/Views/AlbumDetailSongListView.swift @@ -0,0 +1,13 @@ +// +// AlbumDetailSongListView.swift +// Persephone +// +// Created by Daniel Barber on 2019/6/02. +// Copyright © 2019 Dan Barber. All rights reserved. +// + +import AppKit + +class AlbumDetailSongListView: NSTableView { + override func drawGrid(inClipRect clipRect: NSRect) { } +} diff --git a/Persephone/Views/AlbumDetailSongRowView.swift b/Persephone/Views/AlbumDetailSongRowView.swift new file mode 100644 index 0000000..d41decb --- /dev/null +++ b/Persephone/Views/AlbumDetailSongRowView.swift @@ -0,0 +1,27 @@ +// +// AlbumDetailSongRowView.swift +// Persephone +// +// Created by Daniel Barber on 2019/5/22. +// Copyright © 2019 Dan Barber. All rights reserved. +// + +import AppKit + +class AlbumDetailSongRowView: NSTableRowView { + override func drawBackground(in dirtyRect: NSRect) { + let borderPath = CGMutablePath() + let startingPoint = CGPoint(x: dirtyRect.origin.x, y: dirtyRect.height) + let endingPoint = CGPoint(x: dirtyRect.width, y: dirtyRect.height) + + borderPath.move(to: startingPoint) + borderPath.addLine(to: endingPoint) + + let shapeLayer = CAShapeLayer() + self.layer?.addSublayer(shapeLayer) + + shapeLayer.path = borderPath + shapeLayer.strokeColor = NSColor.secondaryLabelColor.cgColor + shapeLayer.lineWidth = 1 + } +}