From 81c4320d3aad89c7dc6a013ef06bd4be804be174 Mon Sep 17 00:00:00 2001 From: Lee Jun Kit Date: Fri, 28 May 2021 12:22:43 +0800 Subject: [PATCH] fix up TrackProgressSlider --- Spottie/ViewModels/FakePlayerViewModel.swift | 6 +-- Spottie/ViewModels/PlayerStateProtocol.swift | 6 +-- Spottie/ViewModels/PlayerViewModel.swift | 52 +++++++++++-------- .../Views/Components/NextTrackButton.swift | 13 ++--- .../Views/Components/PlayPauseButton.swift | 19 ++++--- Spottie/Views/Components/PlayerControls.swift | 17 ++++-- .../Components/PreviousTrackButton.swift | 13 ++--- .../Components/TrackProgressSlider.swift | 52 +++++++++++++------ 8 files changed, 112 insertions(+), 66 deletions(-) diff --git a/Spottie/ViewModels/FakePlayerViewModel.swift b/Spottie/ViewModels/FakePlayerViewModel.swift index 63d9941..b1c743d 100644 --- a/Spottie/ViewModels/FakePlayerViewModel.swift +++ b/Spottie/ViewModels/FakePlayerViewModel.swift @@ -16,7 +16,7 @@ final class FakePlayerViewModel: PlayerStateProtocol { var artistName = "Artist Name" var artworkURL = URL(string: "https://i.scdn.co/image/ab67616d00004851a48964b5d9a3d6968ae3e0de") - func onPlayPauseButtonTapped() {} - func onNextTrackButtonTapped() {} - func onPreviousTrackButtonTapped() {} + func previousTrack() {} + func nextTrack() {} + func togglePlayPause() {} } diff --git a/Spottie/ViewModels/PlayerStateProtocol.swift b/Spottie/ViewModels/PlayerStateProtocol.swift index e5ec685..1f986b1 100644 --- a/Spottie/ViewModels/PlayerStateProtocol.swift +++ b/Spottie/ViewModels/PlayerStateProtocol.swift @@ -16,7 +16,7 @@ protocol PlayerStateProtocol: ObservableObject { var durationMs: Int { get set } var progressMs: Int { get set } - func onPlayPauseButtonTapped() -> Void - func onNextTrackButtonTapped() -> Void - func onPreviousTrackButtonTapped() -> Void + func togglePlayPause() -> Void + func nextTrack() -> Void + func previousTrack() -> Void } diff --git a/Spottie/ViewModels/PlayerViewModel.swift b/Spottie/ViewModels/PlayerViewModel.swift index abe87d7..0e812e1 100644 --- a/Spottie/ViewModels/PlayerViewModel.swift +++ b/Spottie/ViewModels/PlayerViewModel.swift @@ -31,43 +31,53 @@ class PlayerViewModel: PlayerStateProtocol { // subscribe to events eventBroker.onEventReceived.sink(receiveValue: {[weak self] event in + guard let self = self else { return } + switch (event.data) { case .playbackEnded(_): - fallthrough - case .playbackPaused(_): - self?.isPlaying = false - case .playbackResumed(_): - self?.isPlaying = true + self.isPlaying = false + case let .playbackPaused(playbackPausedEvent): + self.progressMs = playbackPausedEvent.trackTime + self.isPlaying = false + case let .playbackResumed(playbackResumedEvent): + self.isPlaying = true + self.progressMs = playbackResumedEvent.trackTime case let .trackChanged(trackChangedEvent): + self.progressMs = 0 + self.isPlaying = true if let track = trackChangedEvent.track { - self?.trackName = track.name - self?.artistName = track.artist[0].name + self.trackName = track.name + self.artistName = track.artist[0].name } case let .metadataAvailable(metadataAvailableEvent): - self?.trackName = metadataAvailableEvent.track.name - self?.artistName = metadataAvailableEvent.track.artist[0].name - self?.artworkURL = metadataAvailableEvent.track.album.coverGroup.getArtworkURL() - self?.durationMs = metadataAvailableEvent.track.duration - self?.progressMs = 0 + self.trackName = metadataAvailableEvent.track.name + self.artistName = metadataAvailableEvent.track.artist[0].name + self.artworkURL = metadataAvailableEvent.track.album.coverGroup.getArtworkURL() + self.durationMs = metadataAvailableEvent.track.duration default: break; } + + print(" in event recevied: self.durationMs: \(self.durationMs) self.progressMs: \(self.progressMs)") }).store(in: &self.cancellables) }) {[weak self] context in + guard let self = self else { return } if let ctx = context { - self?.isPlaying = ctx.isPlaying - self?.trackName = ctx.item.name - self?.artistName = ctx.item.artists[0].name - self?.artworkURL = ctx.item.album.getArtworkURL() - self?.durationMs = ctx.item.durationMs - self?.progressMs = ctx.progressMs + self.isPlaying = ctx.isPlaying + self.trackName = ctx.item.name + self.artistName = ctx.item.artists[0].name + self.artworkURL = ctx.item.album.getArtworkURL() + self.durationMs = ctx.item.durationMs + self.progressMs = ctx.progressMs } + + print(" in value recevied: self.durationMs: \(self.durationMs) self.progressMs: \(self.progressMs)") } token.store(in: &cancellables) } - func onPlayPauseButtonTapped() { + func togglePlayPause() { var apiCall: AnyPublisher; if self.isPlaying { apiCall = SpotifyAPI.pause() @@ -78,11 +88,11 @@ class PlayerViewModel: PlayerStateProtocol { apiCall.print().sink { _ in } receiveValue: { _ in }.store(in: &cancellables) } - func onNextTrackButtonTapped() { + func nextTrack() { SpotifyAPI.nextTrack().sink { _ in } receiveValue: { _ in }.store(in: &cancellables) } - func onPreviousTrackButtonTapped() { + func previousTrack() { SpotifyAPI.previousTrack().sink { _ in } receiveValue: { _ in }.store(in: &cancellables) } } diff --git a/Spottie/Views/Components/NextTrackButton.swift b/Spottie/Views/Components/NextTrackButton.swift index b89a9f9..0c5575c 100644 --- a/Spottie/Views/Components/NextTrackButton.swift +++ b/Spottie/Views/Components/NextTrackButton.swift @@ -7,12 +7,11 @@ import SwiftUI -struct NextTrackButton: View { - @EnvironmentObject var viewModel: M - +struct NextTrackButton: View { + var nextTrackButtonTapped: () -> Void var body: some View { Button(action: { - viewModel.onNextTrackButtonTapped() + self.nextTrackButtonTapped() }) { Image(systemName: "forward.end.fill") .resizable() @@ -23,8 +22,10 @@ struct NextTrackButton: View { } struct NextTrackButton_Previews: PreviewProvider { + static func nextTrackButtonTapped() { + print("nextTrackButtonTapped") + } static var previews: some View { - NextTrackButton() - .environmentObject(FakePlayerViewModel()) + NextTrackButton(nextTrackButtonTapped: nextTrackButtonTapped) } } diff --git a/Spottie/Views/Components/PlayPauseButton.swift b/Spottie/Views/Components/PlayPauseButton.swift index b07a2fb..1f2ce04 100644 --- a/Spottie/Views/Components/PlayPauseButton.swift +++ b/Spottie/Views/Components/PlayPauseButton.swift @@ -8,14 +8,15 @@ import SwiftUI import Combine -struct PlayPauseButton: View { - @EnvironmentObject var viewModel: M +struct PlayPauseButton: View { + var playPauseButtonTapped: () -> Void + var isPlaying: Bool var body: some View { Button(action: { - viewModel.onPlayPauseButtonTapped() + playPauseButtonTapped() }) { - Image(systemName: viewModel.isPlaying ? "pause.circle.fill" : "play.circle.fill") + Image(systemName: isPlaying ? "pause.circle.fill" : "play.circle.fill") .resizable() .clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/) .frame(width: 32, height: 32) @@ -25,10 +26,14 @@ struct PlayPauseButton: View { } struct PlayPauseButton_Previews: PreviewProvider { - + static func playPauseButtonTapped() { + print("playPauseButtonTapped") + } static var previews: some View { - PlayPauseButton() - .environmentObject(FakePlayerViewModel()) + PlayPauseButton( + playPauseButtonTapped: playPauseButtonTapped, + isPlaying: true + ) } } diff --git a/Spottie/Views/Components/PlayerControls.swift b/Spottie/Views/Components/PlayerControls.swift index d73c578..f588a49 100644 --- a/Spottie/Views/Components/PlayerControls.swift +++ b/Spottie/Views/Components/PlayerControls.swift @@ -8,16 +8,25 @@ import SwiftUI struct PlayerControls: View { + @EnvironmentObject var viewModel: M + var body: some View { VStack { HStack(spacing: 28) { ShuffleButton() - PreviousTrackButton() - PlayPauseButton() - NextTrackButton() + PreviousTrackButton( + previousTrackButtonTapped: viewModel.previousTrack + ) + PlayPauseButton( + playPauseButtonTapped: viewModel.togglePlayPause, + isPlaying: viewModel.isPlaying + ) + NextTrackButton( + nextTrackButtonTapped: viewModel.nextTrack + ) RepeatButton() } - TrackProgressSlider() + TrackProgressSlider(viewModel: TrackProgressSlider.ViewModel(isPlaying: viewModel.isPlaying, progressMs: viewModel.progressMs, durationMs: viewModel.durationMs)) .padding(.leading) .padding(.trailing) } diff --git a/Spottie/Views/Components/PreviousTrackButton.swift b/Spottie/Views/Components/PreviousTrackButton.swift index f052e66..148d05d 100644 --- a/Spottie/Views/Components/PreviousTrackButton.swift +++ b/Spottie/Views/Components/PreviousTrackButton.swift @@ -7,12 +7,11 @@ import SwiftUI -struct PreviousTrackButton: View { - @EnvironmentObject var viewModel: M - +struct PreviousTrackButton: View { + var previousTrackButtonTapped: () -> Void var body: some View { Button(action: { - viewModel.onPreviousTrackButtonTapped() + self.previousTrackButtonTapped() }) { Image(systemName: "backward.end.fill") .resizable() @@ -23,8 +22,10 @@ struct PreviousTrackButton: View { } struct PreviousTrackButton_Previews: PreviewProvider { + static func onPreviousTrackTapped() { + print("onPreviousTrackTapped") + } static var previews: some View { - PreviousTrackButton() - .environmentObject(FakePlayerViewModel()) + PreviousTrackButton(previousTrackButtonTapped: onPreviousTrackTapped) } } diff --git a/Spottie/Views/Components/TrackProgressSlider.swift b/Spottie/Views/Components/TrackProgressSlider.swift index 7e5be78..9f085f3 100644 --- a/Spottie/Views/Components/TrackProgressSlider.swift +++ b/Spottie/Views/Components/TrackProgressSlider.swift @@ -8,40 +8,32 @@ import SwiftUI struct TrackProgressSlider: View { - @State var isPlaying = true - @State var progressMs = 0 - @State var durationMs = 60000 - @State var progressPercent = 0.0 - + @ObservedObject var viewModel: TrackProgressSlider.ViewModel + let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() var prettyProgress: String { get { - return TrackProgressSlider.DurationFormatter.shared().string(from: Double(self.progressMs) / 1000.0)! + return TrackProgressSlider.DurationFormatter.shared().string(from: Double(self.viewModel.progressMs) / 1000.0)! } } var prettyDuration: String { get { - return TrackProgressSlider.DurationFormatter.shared().string(from: Double(self.durationMs) / 1000.0)! + return TrackProgressSlider.DurationFormatter.shared().string(from: Double(self.viewModel.durationMs) / 1000.0)! } } var body: some View { Slider( - value: $progressPercent, + value: $viewModel.progressPercent, minimumValueLabel: Text(prettyProgress).foregroundColor(.secondary), maximumValueLabel: Text(prettyDuration).foregroundColor(.secondary) ) { Text("") } - .onReceive(timer) { timer in - if isPlaying { - if (progressMs < durationMs) { - progressMs += 1000 - progressPercent = Double(self.progressMs) / Double(self.durationMs) - } - } + .onReceive(timer) { _ in + viewModel.updateProgress() } } } @@ -60,10 +52,38 @@ extension TrackProgressSlider { return sharedDurationFormatter } } + + class ViewModel: ObservableObject { + @Published var isPlaying: Bool + @Published var progressMs: Int + @Published var durationMs: Int + @Published var progressPercent: Double + + init(isPlaying: Bool, progressMs: Int, durationMs: Int) { + self.isPlaying = isPlaying + self.progressMs = progressMs + self.durationMs = durationMs + self.progressPercent = Double(progressMs) / Double(durationMs) + } + + func calculateProgressPercent() { + self.progressPercent = Double(self.progressMs) / Double(self.durationMs) + } + + func updateProgress() { + if (self.isPlaying) { + if (self.progressMs < self.durationMs) { + self.progressMs += 1000 + self.calculateProgressPercent() + } + } + } + } } struct TrackProgressSlider_Previews: PreviewProvider { + static let viewModel = TrackProgressSlider.ViewModel(isPlaying: true, progressMs: 20000, durationMs: 120000) static var previews: some View { - TrackProgressSlider() + TrackProgressSlider(viewModel: viewModel) } }