From 5253d3181b2796afa98d6b7b34ae71a65b9b456d Mon Sep 17 00:00:00 2001 From: Mohamed Afifi Date: Sat, 28 Sep 2024 16:32:16 -0400 Subject: [PATCH] Remove delegate pattern from RemoteCommandsHandler --- .../AudioBannerBuilder.swift | 1 - .../AudioBannerViewModel.swift | 63 ++++++++------- .../RemoteCommandsHandler.swift | 81 ++++++++++++------- 3 files changed, 87 insertions(+), 58 deletions(-) diff --git a/Features/AudioBannerFeature/AudioBannerBuilder.swift b/Features/AudioBannerFeature/AudioBannerBuilder.swift index 6ab71b38..a416629e 100644 --- a/Features/AudioBannerFeature/AudioBannerBuilder.swift +++ b/Features/AudioBannerFeature/AudioBannerBuilder.swift @@ -34,7 +34,6 @@ public struct AudioBannerBuilder { baseURL: container.filesAppHost, downloader: container.downloadManager ), - remoteCommandsHandler: RemoteCommandsHandler(center: .shared()), reciterListBuilder: ReciterListBuilder(), advancedAudioOptionsBuilder: AdvancedAudioOptionsBuilder() ) diff --git a/Features/AudioBannerFeature/AudioBannerViewModel.swift b/Features/AudioBannerFeature/AudioBannerViewModel.swift index 9a8049cf..1855541b 100644 --- a/Features/AudioBannerFeature/AudioBannerViewModel.swift +++ b/Features/AudioBannerFeature/AudioBannerViewModel.swift @@ -50,7 +50,6 @@ public final class AudioBannerViewModel: ObservableObject { recentRecitersService: RecentRecitersService, audioPlayer: QuranAudioPlayer, downloader: QuranAudioDownloader, - remoteCommandsHandler: RemoteCommandsHandler, reciterListBuilder: ReciterListBuilder, advancedAudioOptionsBuilder: AdvancedAudioOptionsBuilder ) { @@ -59,24 +58,11 @@ public final class AudioBannerViewModel: ObservableObject { self.recentRecitersService = recentRecitersService self.audioPlayer = audioPlayer self.downloader = downloader - self.remoteCommandsHandler = remoteCommandsHandler self.reciterListBuilder = reciterListBuilder self.advancedAudioOptionsBuilder = advancedAudioOptionsBuilder - let actions = QuranAudioPlayerActions( - playbackEnded: { [weak self] in self?.playbackEnded() }, - playbackPaused: { [weak self] in self?.playbackPaused() }, - playbackResumed: { [weak self] in self?.playbackResumed() }, - playing: { [weak self] in self?.playing(ayah: $0) } - ) - audioPlayer.setActions(actions) - - remoteCommandsHandler.delegate = self - } - - deinit { - remoteCommandsHandler.stopListening() - NotificationCenter.default.removeObserver(self) + setUpAudioPlayerActions() + setUpRemoteCommandHandler() } // MARK: Public @@ -106,7 +92,7 @@ public final class AudioBannerViewModel: ObservableObject { } func start() async { - remoteCommandsHandler.startListeningToPlayCommand() + remoteCommandsHandler?.startListeningToPlayCommand() NotificationCenter.default.addObserver( self, selector: #selector(applicationDidBecomeActive), @@ -142,7 +128,7 @@ public final class AudioBannerViewModel: ObservableObject { private let lastAyahFinder: LastAyahFinder = PreferencesLastAyahFinder.shared private let audioPlayer: QuranAudioPlayer private let downloader: QuranAudioDownloader - private let remoteCommandsHandler: RemoteCommandsHandler + private var remoteCommandsHandler: RemoteCommandsHandler? private let reciterListBuilder: ReciterListBuilder private let advancedAudioOptionsBuilder: AdvancedAudioOptionsBuilder @@ -177,8 +163,8 @@ public final class AudioBannerViewModel: ObservableObject { } listener?.highlightReadingAyah(nil) - remoteCommandsHandler.stopListening() - remoteCommandsHandler.startListeningToPlayCommand() + remoteCommandsHandler?.stopListening() + remoteCommandsHandler?.startListeningToPlayCommand() } @objc @@ -188,8 +174,6 @@ public final class AudioBannerViewModel: ObservableObject { playingState = tempPlayingState } - // MARK: - Reciter - private func selectReciter(_ reciter: Reciter) { preferences.lastSelectedReciterId = reciter.id } @@ -311,6 +295,16 @@ public final class AudioBannerViewModel: ObservableObject { // MARK: - Audio Interactor Delegate + private func setUpAudioPlayerActions() { + let actions = QuranAudioPlayerActions( + playbackEnded: { [weak self] in self?.playbackEnded() }, + playbackPaused: { [weak self] in self?.playbackPaused() }, + playbackResumed: { [weak self] in self?.playbackResumed() }, + playing: { [weak self] in self?.playing(ayah: $0) } + ) + audioPlayer.setActions(actions) + } + private func playbackPaused() { logger.info("AudioBanner: playback paused") updatePlayingState(to: .paused) @@ -363,7 +357,7 @@ public final class AudioBannerViewModel: ObservableObject { logger.info("AudioBanner: playing started") cancellableTasks = [] crasher.setValue(false, forKey: .downloadingQuran) - remoteCommandsHandler.startListening() + remoteCommandsHandler?.startListening() playingState = .playing guard let audioRange else { @@ -425,8 +419,19 @@ extension AudioBannerViewModel { } } -extension AudioBannerViewModel: RemoteCommandsHandlerDelegate { - func onPlayCommandFired() { +extension AudioBannerViewModel { + private func setUpRemoteCommandHandler() { + let remoteActions = RemoteCommandActions( + play: { [weak self] in self?.handlePlayCommand() }, + pause: { [weak self] in self?.handlePauseCommand() }, + togglePlayPause: { [weak self] in self?.handleTogglePlayPauseCommand() }, + nextTrack: { [weak self] in self?.handleNextTrackCommand() }, + previousTrack: { [weak self] in self?.handlePreviousTrackCommand() } + ) + remoteCommandsHandler = RemoteCommandsHandler(center: .shared(), actions: remoteActions) + } + + private func handlePlayCommand() { logger.info("AudioBanner: play command fired. State: \(playingState)") switch playingState { case .stopped: playStartingCurrentPage() @@ -435,22 +440,22 @@ extension AudioBannerViewModel: RemoteCommandsHandlerDelegate { } } - func onPauseCommandFired() { + private func handlePauseCommand() { logger.info("AudioBanner: pause command fired. State: \(playingState)") pause() } - func onTogglePlayPauseCommandFired() { + private func handleTogglePlayPauseCommand() { logger.info("AudioBanner: toggle play/pause command fired. State: \(playingState)") togglePlayPause() } - func onStepForwardCommandFired() { + private func handleNextTrackCommand() { logger.info("AudioBanner: step forward command fired. State: \(playingState)") stepForward() } - func onStepBackwardCommandFire() { + private func handlePreviousTrackCommand() { logger.info("AudioBanner: step backward command fired. State: \(playingState)") stepBackward() } diff --git a/Features/AudioBannerFeature/RemoteCommandsHandler.swift b/Features/AudioBannerFeature/RemoteCommandsHandler.swift index 84ca7c11..585a8e45 100644 --- a/Features/AudioBannerFeature/RemoteCommandsHandler.swift +++ b/Features/AudioBannerFeature/RemoteCommandsHandler.swift @@ -9,36 +9,38 @@ import MediaPlayer @MainActor -protocol RemoteCommandsHandlerDelegate: AnyObject { - func onPlayCommandFired() - func onPauseCommandFired() - func onTogglePlayPauseCommandFired() - func onStepForwardCommandFired() - func onStepBackwardCommandFire() +struct RemoteCommandActions { + var play: () -> Void + var pause: () -> Void + var togglePlayPause: () -> Void + var nextTrack: () -> Void + var previousTrack: () -> Void } -final class RemoteCommandsHandler: Sendable { +@MainActor +final class RemoteCommandsHandler { // MARK: Lifecycle - init(center: MPRemoteCommandCenter) { + init(center: MPRemoteCommandCenter, actions: RemoteCommandActions) { self.center = center + self.actions = actions setUpRemoteControlEvents() } deinit { - stopListening() + Task { [center] in + await center.setCommandsEnabled(false) + } } // MARK: Internal - weak var delegate: RemoteCommandsHandlerDelegate? - func startListening() { - setCommandsEnabled(true) + center.setCommandsEnabled(true) } func stopListening() { - setCommandsEnabled(false) + center.setCommandsEnabled(false) } func startListeningToPlayCommand() { @@ -48,56 +50,79 @@ final class RemoteCommandsHandler: Sendable { // MARK: Private private let center: MPRemoteCommandCenter + private let actions: RemoteCommandActions private func setUpRemoteControlEvents() { center.playCommand.addTarget { [weak self] _ in guard let self else { return .success } Task { @MainActor in - self.delegate?.onPlayCommandFired() + self.actions.play() } return .success } center.pauseCommand.addTarget { [weak self] _ in guard let self else { return .success } Task { @MainActor in - self.delegate?.onPauseCommandFired() + self.actions.pause() } return .success } center.togglePlayPauseCommand.addTarget { [weak self] _ in guard let self else { return .success } Task { @MainActor in - self.delegate?.onTogglePlayPauseCommandFired() + self.actions.togglePlayPause() } return .success } center.nextTrackCommand.addTarget { [weak self] _ in guard let self else { return .success } Task { @MainActor in - self.delegate?.onStepForwardCommandFired() + self.actions.nextTrack() } return .success } center.previousTrackCommand.addTarget { [weak self] _ in guard let self else { return .success } Task { @MainActor in - self.delegate?.onStepBackwardCommandFire() + self.actions.previousTrack() } return .success } // disable all of them initially - setCommandsEnabled(false) - - // disabled unused command - [center.seekForwardCommand, center.seekBackwardCommand, center.skipForwardCommand, - center.skipBackwardCommand, center.ratingCommand, center.changePlaybackRateCommand, - center.likeCommand, center.dislikeCommand, center.bookmarkCommand, center.changePlaybackPositionCommand].forEach { $0.isEnabled = false } + center.setCommandsEnabled(false) + + // disabled unused commands + let unusedCommands = [ + center.seekForwardCommand, + center.seekBackwardCommand, + center.skipForwardCommand, + center.skipBackwardCommand, + center.ratingCommand, + center.changePlaybackRateCommand, + center.likeCommand, + center.dislikeCommand, + center.bookmarkCommand, + center.changePlaybackPositionCommand, + ] + for command in unusedCommands { + command.isEnabled = false + } } +} - private func setCommandsEnabled(_ enabled: Bool) { - let center = MPRemoteCommandCenter.shared() - [center.playCommand, center.pauseCommand, center.togglePlayPauseCommand, - center.nextTrackCommand, center.previousTrackCommand].forEach { $0.isEnabled = enabled } +extension MPRemoteCommandCenter { + @MainActor + func setCommandsEnabled(_ enabled: Bool) { + let commands = [ + playCommand, + pauseCommand, + togglePlayPauseCommand, + nextTrackCommand, + previousTrackCommand, + ] + for command in commands { + command.isEnabled = enabled + } } }