Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove delegate pattern from RemoteCommandsHandler #670

Merged
merged 1 commit into from
Sep 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Features/AudioBannerFeature/AudioBannerBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ public struct AudioBannerBuilder {
baseURL: container.filesAppHost,
downloader: container.downloadManager
),
remoteCommandsHandler: RemoteCommandsHandler(center: .shared()),
reciterListBuilder: ReciterListBuilder(),
advancedAudioOptionsBuilder: AdvancedAudioOptionsBuilder()
)
Expand Down
63 changes: 34 additions & 29 deletions Features/AudioBannerFeature/AudioBannerViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ public final class AudioBannerViewModel: ObservableObject {
recentRecitersService: RecentRecitersService,
audioPlayer: QuranAudioPlayer,
downloader: QuranAudioDownloader,
remoteCommandsHandler: RemoteCommandsHandler,
reciterListBuilder: ReciterListBuilder,
advancedAudioOptionsBuilder: AdvancedAudioOptionsBuilder
) {
Expand All @@ -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
Expand Down Expand Up @@ -106,7 +92,7 @@ public final class AudioBannerViewModel: ObservableObject {
}

func start() async {
remoteCommandsHandler.startListeningToPlayCommand()
remoteCommandsHandler?.startListeningToPlayCommand()
NotificationCenter.default.addObserver(
self,
selector: #selector(applicationDidBecomeActive),
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -177,8 +163,8 @@ public final class AudioBannerViewModel: ObservableObject {
}
listener?.highlightReadingAyah(nil)

remoteCommandsHandler.stopListening()
remoteCommandsHandler.startListeningToPlayCommand()
remoteCommandsHandler?.stopListening()
remoteCommandsHandler?.startListeningToPlayCommand()
}

@objc
Expand All @@ -188,8 +174,6 @@ public final class AudioBannerViewModel: ObservableObject {
playingState = tempPlayingState
}

// MARK: - Reciter

private func selectReciter(_ reciter: Reciter) {
preferences.lastSelectedReciterId = reciter.id
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
Expand All @@ -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()
}
Expand Down
81 changes: 53 additions & 28 deletions Features/AudioBannerFeature/RemoteCommandsHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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
}
}
}
Loading