parent
1cf5353339
commit
5b8cdf603c
@ -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…
Reference in new issue