feat: add PlaylistViewModel with reactive playlist observation

feat/music-streaming
Laurent 1 month ago
parent 1cf5353339
commit 5b8cdf603c
  1. 122
      Music/ViewModels/PlaylistViewModel.swift

@ -0,0 +1,122 @@
import Foundation
import Observation
import GRDB
@Observable
final class PlaylistViewModel {
var playlists: [Playlist] = []
var selectedPlaylist: Playlist?
var playlistTracks: [Track] = []
var lastUsedPlaylistId: Int64? {
get { UserDefaults.standard.object(forKey: "lastUsedPlaylistId") as? Int64 }
set { UserDefaults.standard.set(newValue, forKey: "lastUsedPlaylistId") }
}
var lastUsedPlaylistName: String? {
guard let id = lastUsedPlaylistId else { return nil }
return playlists.first(where: { $0.id == id })?.name
}
private let db: DatabaseService
private var playlistsCancellable: AnyDatabaseCancellable?
private var tracksCancellable: AnyDatabaseCancellable?
private var searchTask: Task<Void, Never>?
private var searchText = ""
init(db: DatabaseService) {
self.db = db
observePlaylists()
}
func createPlaylist(name: String) throws {
_ = try db.createPlaylist(name: name)
}
func renamePlaylist(_ playlist: Playlist, to name: String) throws {
guard let id = playlist.id else { return }
try db.renamePlaylist(id: id, name: name)
}
func deletePlaylist(_ playlist: Playlist) throws {
guard let id = playlist.id else { return }
if selectedPlaylist?.id == id {
deselectPlaylist()
}
if lastUsedPlaylistId == id {
lastUsedPlaylistId = nil
}
try db.deletePlaylist(id: id)
}
func addTrack(_ track: Track, to playlist: Playlist) throws {
guard let trackId = track.id, let playlistId = playlist.id else { return }
try db.addTrackToPlaylist(trackId: trackId, playlistId: playlistId)
lastUsedPlaylistId = playlistId
}
func addTrackToLastUsedPlaylist(_ track: Track) throws {
guard let playlistId = lastUsedPlaylistId,
let playlist = playlists.first(where: { $0.id == playlistId }) else { return }
try addTrack(track, to: playlist)
}
func removeTrack(_ track: Track, from playlist: Playlist) throws {
guard let trackId = track.id, let playlistId = playlist.id else { return }
try db.removeTrackFromPlaylist(trackId: trackId, playlistId: playlistId)
}
func moveTrack(in playlist: Playlist, from source: Int, to destination: Int) throws {
guard let playlistId = playlist.id else { return }
try db.reorderPlaylistTrack(playlistId: playlistId, fromPosition: source, toPosition: destination)
}
func search(_ text: String) {
searchText = text
searchTask?.cancel()
searchTask = Task { @MainActor [weak self] in
try? await Task.sleep(for: .milliseconds(150))
guard !Task.isCancelled else { return }
self?.observePlaylistTracks()
}
}
func selectPlaylist(_ playlist: Playlist) {
selectedPlaylist = playlist
observePlaylistTracks()
}
func deselectPlaylist() {
selectedPlaylist = nil
tracksCancellable?.cancel()
tracksCancellable = nil
playlistTracks = []
searchText = ""
}
private func observePlaylists() {
let observation = ValueObservation.tracking { [db] dbAccess in
try db.fetchPlaylists(db: dbAccess)
}
playlistsCancellable = observation.start(
in: db.dbPool,
onError: { error in print("Playlists observation error: \(error)") },
onChange: { [weak self] playlists in self?.playlists = playlists }
)
}
private func observePlaylistTracks() {
tracksCancellable?.cancel()
guard let playlistId = selectedPlaylist?.id else { return }
let search = searchText
let observation = ValueObservation.tracking { [db] dbAccess in
try db.fetchPlaylistTracks(db: dbAccess, playlistId: playlistId, search: search)
}
tracksCancellable = observation.start(
in: db.dbPool,
onError: { error in print("Playlist tracks observation error: \(error)") },
onChange: { [weak self] tracks in self?.playlistTracks = tracks }
)
}
}
Loading…
Cancel
Save