diff --git a/Persephone/AppDelegate.swift b/Persephone/AppDelegate.swift index 883d708..dd321b4 100644 --- a/Persephone/AppDelegate.swift +++ b/Persephone/AppDelegate.swift @@ -18,7 +18,7 @@ class AppDelegate: NSObject, func applicationDidFinishLaunching(_ aNotification: Notification) { App.mpdServerController.connect() - _ = App.userNotificationsController + instantiateUserNotificationsController() mediaKeyTap = MediaKeyTap(delegate: self) mediaKeyTap?.start() @@ -30,6 +30,10 @@ class AppDelegate: NSObject, } } + func instantiateUserNotificationsController() { + _ = App.userNotificationsController + } + func applicationWillTerminate(_ aNotification: Notification) { App.mpdServerController.disconnect() } diff --git a/Persephone/Controllers/WindowController.swift b/Persephone/Controllers/WindowController.swift index 7ff5f21..b126160 100644 --- a/Persephone/Controllers/WindowController.swift +++ b/Persephone/Controllers/WindowController.swift @@ -17,6 +17,16 @@ class WindowController: NSWindowController { var state: MPDClient.MPDStatus.State? var trackTimer: Timer? + @IBOutlet var transportControls: NSSegmentedCell! + + @IBOutlet var trackProgress: NSTextField! + @IBOutlet var trackProgressBar: NSSlider! + @IBOutlet var trackRemaining: NSTextField! + @IBOutlet var databaseUpdatingIndicator: NSProgressIndicator! + + @IBOutlet var shuffleState: NSButton! + @IBOutlet var repeatState: NSButton! + override func windowDidLoad() { super.windowDidLoad() window?.titleVisibility = .hidden @@ -137,12 +147,14 @@ class WindowController: NSWindowController { } } - @IBOutlet var transportControls: NSSegmentedCell! - - @IBOutlet var trackProgress: NSTextField! - @IBOutlet var trackProgressBar: NSSlider! - @IBOutlet var trackRemaining: NSTextField! - @IBOutlet var databaseUpdatingIndicator: NSProgressIndicator! + @IBAction func handleShuffleButton(_ sender: NSButton) { + App.store.dispatch(MPDSetShuffleAction(shuffleState: sender.state == .on)) + } + + @IBAction func handleRepeatButton(_ sender: NSButton) { + App.store.dispatch(MPDSetRepeatAction(repeatState: sender.state == .on)) + } + } extension WindowController: NSWindowDelegate { diff --git a/Persephone/MPDClient/Extensions/MPDClient+Command.swift b/Persephone/MPDClient/Extensions/MPDClient+Command.swift index 8eadda1..f5b0cca 100644 --- a/Persephone/MPDClient/Extensions/MPDClient+Command.swift +++ b/Persephone/MPDClient/Extensions/MPDClient+Command.swift @@ -29,6 +29,16 @@ extension MPDClient { else { return } sendSeekCurrentSong(timeInSeconds: timeInSeconds) + case .setShuffleState: + guard let shuffleState = userData["shuffleState"] as? Bool + else { return } + sendShuffleState(shuffleState: shuffleState) + + case .setRepeatState: + guard let repeatState = userData["repeatState"] as? Bool + else { return } + sendRepeatState(repeatState: repeatState) + // Database commands case .updateDatabase: sendUpdateDatabase() diff --git a/Persephone/MPDClient/Extensions/MPDClient+Transport.swift b/Persephone/MPDClient/Extensions/MPDClient+Transport.swift index cb53fa9..2c97a82 100644 --- a/Persephone/MPDClient/Extensions/MPDClient+Transport.swift +++ b/Persephone/MPDClient/Extensions/MPDClient+Transport.swift @@ -33,6 +33,20 @@ extension MPDClient { ) } + func setShuffleState(shuffleState: Bool) { + enqueueCommand( + command: .setShuffleState, + userData: ["shuffleState": shuffleState] + ) + } + + func setRepeatState(repeatState: Bool) { + enqueueCommand( + command: .setRepeatState, + userData: ["repeatState": repeatState] + ) + } + func sendNextTrack() { guard let state = status?.state, state.isOneOf([.playing, .paused]) @@ -64,4 +78,12 @@ extension MPDClient { func sendSeekCurrentSong(timeInSeconds: Float) { mpd_run_seek_current(self.connection, timeInSeconds, false) } + + func sendShuffleState(shuffleState: Bool) { + mpd_run_random(self.connection, shuffleState) + } + + func sendRepeatState(repeatState: Bool) { + mpd_run_repeat(self.connection, repeatState) + } } diff --git a/Persephone/MPDClient/Models/MPDCommand.swift b/Persephone/MPDClient/Models/MPDCommand.swift index 2631a93..b938056 100644 --- a/Persephone/MPDClient/Models/MPDCommand.swift +++ b/Persephone/MPDClient/Models/MPDCommand.swift @@ -17,6 +17,9 @@ extension MPDClient { case stop case seekCurrentSong + case setShuffleState + case setRepeatState + // Database commands case updateDatabase diff --git a/Persephone/Resources/Base.lproj/Main.storyboard b/Persephone/Resources/Base.lproj/Main.storyboard index 58bf652..b032e35 100644 --- a/Persephone/Resources/Base.lproj/Main.storyboard +++ b/Persephone/Resources/Base.lproj/Main.storyboard @@ -222,9 +222,44 @@ + + + + + + + + + + + + + + + @@ -239,6 +274,8 @@ + + @@ -749,6 +786,8 @@ + + diff --git a/Persephone/State/Actions/MPDActions.swift b/Persephone/State/Actions/MPDActions.swift index e4be191..f13fb37 100644 --- a/Persephone/State/Actions/MPDActions.swift +++ b/Persephone/State/Actions/MPDActions.swift @@ -29,3 +29,11 @@ struct MPDSeekCurrentSong: Action { } struct MPDUpdateDatabaseAction: Action {} + +struct MPDSetShuffleAction: Action { + let shuffleState: Bool +} + +struct MPDSetRepeatAction: Action { + let repeatState: Bool +} diff --git a/Persephone/State/Actions/PlayerActions.swift b/Persephone/State/Actions/PlayerActions.swift index 76a12f2..6fa3991 100644 --- a/Persephone/State/Actions/PlayerActions.swift +++ b/Persephone/State/Actions/PlayerActions.swift @@ -24,3 +24,11 @@ struct UpdateElapsedTimeAction: Action { struct UpdateStatusAction: Action { var status: MPDClient.MPDStatus } + +struct UpdateShuffleAction: Action { + var shuffleState: Bool +} + +struct UpdateRepeatAction: Action { + var repeatState: Bool +} diff --git a/Persephone/State/PlayerState.swift b/Persephone/State/PlayerState.swift index 5c9aed1..6c58766 100644 --- a/Persephone/State/PlayerState.swift +++ b/Persephone/State/PlayerState.swift @@ -15,6 +15,8 @@ struct PlayerState: StateType { var currentArtwork: NSImage? var state: MPDClient.MPDStatus.State? + var shuffleState: Bool = false + var repeatState: Bool = false var totalTime: UInt? var elapsedTimeMs: UInt? @@ -24,6 +26,8 @@ extension PlayerState: Equatable { static func == (lhs: PlayerState, rhs: PlayerState) -> Bool { return (lhs.state == rhs.state) && (lhs.totalTime == rhs.totalTime) && - (lhs.elapsedTimeMs == rhs.elapsedTimeMs) + (lhs.elapsedTimeMs == rhs.elapsedTimeMs) && + (lhs.shuffleState == rhs.shuffleState) && + (lhs.repeatState == rhs.repeatState) } } diff --git a/Persephone/State/Reducers/MPDReducer.swift b/Persephone/State/Reducers/MPDReducer.swift index aa26ff8..3e85308 100644 --- a/Persephone/State/Reducers/MPDReducer.swift +++ b/Persephone/State/Reducers/MPDReducer.swift @@ -39,6 +39,12 @@ func mpdReducer(action: Action, state: MPDState?) -> MPDState { case let action as MPDSeekCurrentSong: App.mpdClient.seekCurrentSong(timeInSeconds: action.timeInSeconds) + case let action as MPDSetShuffleAction: + App.mpdClient.setShuffleState(shuffleState: action.shuffleState) + + case let action as MPDSetRepeatAction: + App.mpdClient.setRepeatState(repeatState: action.repeatState) + case is MPDUpdateDatabaseAction: App.mpdClient.updateDatabase() diff --git a/Persephone/State/Reducers/PlayerReducer.swift b/Persephone/State/Reducers/PlayerReducer.swift index d468407..eecde2a 100644 --- a/Persephone/State/Reducers/PlayerReducer.swift +++ b/Persephone/State/Reducers/PlayerReducer.swift @@ -60,6 +60,12 @@ func playerReducer(action: Action, state: PlayerState?) -> PlayerState { case let action as UpdateElapsedTimeAction: state.elapsedTimeMs = action.elapsedTimeMs + case let action as UpdateShuffleAction: + state.shuffleState = action.shuffleState + + case let action as UpdateRepeatAction: + state.repeatState = action.repeatState + default: break } diff --git a/Resources/export/repeatButton.pdf b/Resources/export/repeatButton.pdf new file mode 100644 index 0000000..2a8bf58 Binary files /dev/null and b/Resources/export/repeatButton.pdf differ diff --git a/Resources/export/repeatButton.png b/Resources/export/repeatButton.png new file mode 100644 index 0000000..ac2ad96 Binary files /dev/null and b/Resources/export/repeatButton.png differ diff --git a/Resources/export/repeatButton1.pdf b/Resources/export/repeatButton1.pdf new file mode 100644 index 0000000..bcd790e Binary files /dev/null and b/Resources/export/repeatButton1.pdf differ diff --git a/Resources/export/repeatButton1.png b/Resources/export/repeatButton1.png new file mode 100644 index 0000000..a9a672b Binary files /dev/null and b/Resources/export/repeatButton1.png differ diff --git a/Resources/export/repeatButton1@2x.png b/Resources/export/repeatButton1@2x.png new file mode 100644 index 0000000..08810d0 Binary files /dev/null and b/Resources/export/repeatButton1@2x.png differ diff --git a/Resources/export/repeatButton@2x.png b/Resources/export/repeatButton@2x.png new file mode 100644 index 0000000..40f668f Binary files /dev/null and b/Resources/export/repeatButton@2x.png differ diff --git a/Resources/export/shuffleButton.pdf b/Resources/export/shuffleButton.pdf new file mode 100644 index 0000000..56b78f1 Binary files /dev/null and b/Resources/export/shuffleButton.pdf differ diff --git a/Resources/export/shuffleButton.png b/Resources/export/shuffleButton.png new file mode 100644 index 0000000..df7bb8d Binary files /dev/null and b/Resources/export/shuffleButton.png differ diff --git a/Resources/export/shuffleButton@2x.png b/Resources/export/shuffleButton@2x.png new file mode 100644 index 0000000..4f6c2d1 Binary files /dev/null and b/Resources/export/shuffleButton@2x.png differ diff --git a/Resources/icons.sketch b/Resources/icons.sketch index 44a3c54..f7c8c5d 100644 Binary files a/Resources/icons.sketch and b/Resources/icons.sketch differ