diff --git a/Music Tools/Model/Playlist.swift b/Music Tools/Model/Playlist.swift index b64928c..b1cf38e 100644 --- a/Music Tools/Model/Playlist.swift +++ b/Music Tools/Model/Playlist.swift @@ -10,58 +10,130 @@ import Foundation import UIKit import SwiftyJSON -class Playlist: Identifiable, Equatable, Codable { +class Playlist: Identifiable, Equatable, Codable, ObservableObject { //MARK: Properties - var name: String - var uri: String - var username: String? + @Published var name: String + @Published var uri: String + @Published var username: String? - var include_recommendations: Bool - var recommendation_sample: Int - var include_library_tracks: Bool + @Published var type: String { + didSet { + self.updatePlaylist(updates: JSON(["type": self.type])) + } + } - var parts: Array - var playlist_references: Array - var shuffle: Bool + @Published var include_recommendations: Bool { + didSet { + self.updatePlaylist(updates: JSON(["include_recommendations": self.include_recommendations])) + } + } + @Published var recommendation_sample: Int{ + didSet { + self.updatePlaylist(updates: JSON(["recommendation_sample": self.recommendation_sample])) + } + } + @Published var include_library_tracks: Bool{ + didSet { + self.updatePlaylist(updates: JSON(["include_library_tracks": self.include_library_tracks])) + } + } - var sort: String - var description_overwrite: String? - var description_suffix: String? + @Published var parts: Array{ + didSet { + self.updatePlaylist(updates: JSON(["parts": self.parts])) + } + } + @Published var playlist_references: Array{ + didSet { + self.updatePlaylist(updates: JSON(["playlist_references": self.playlist_references])) + } + } + @Published var shuffle: Bool{ + didSet { + self.updatePlaylist(updates: JSON(["shuffle": self.shuffle])) + } + } - var last_updated: String? + var sort: String? + @Published var description_overwrite: String? + @Published var description_suffix: String? - var lastfm_stat_count: Int - var lastfm_stat_album_count: Int - var lastfm_stat_artist_count: Int + @Published var last_updated: String? - var lastfm_stat_percent: Float + @Published var lastfm_stat_count: Int + @Published var lastfm_stat_album_count: Int + @Published var lastfm_stat_artist_count: Int + + @Published var lastfm_stat_percent: Float var lastfm_stat_percent_str: String { get { return String(format: "%.2f%%", lastfm_stat_percent) } } - var lastfm_stat_album_percent: Float + @Published var lastfm_stat_album_percent: Float var lastfm_stat_album_percent_str: String { get { return String(format: "%.2f%%", lastfm_stat_album_percent) } } - var lastfm_stat_artist_percent: Float + @Published var lastfm_stat_artist_percent: Float var lastfm_stat_artist_percent_str: String { get { return String(format: "%.2f%%", lastfm_stat_artist_percent) } } - var lastfm_stat_last_refresh: String? + @Published var lastfm_stat_last_refresh: String? + + @Published var add_last_month: Bool{ + didSet { + self.updatePlaylist(updates: JSON(["add_last_month": self.add_last_month])) + } + } + @Published var add_this_month: Bool{ + didSet { + self.updatePlaylist(updates: JSON(["add_this_month": self.add_this_month])) + } + } + @Published var day_boundary: Int{ + didSet { + self.updatePlaylist(updates: JSON(["day_boundary": self.day_boundary])) + } + } + + @Published var chart_range: LastFmRange{ + didSet { + self.updatePlaylist(updates: JSON(["chart_range": self.chart_range.rawValue])) + } + } + @Published var chart_limit: Int{ + didSet { + self.updatePlaylist(updates: JSON(["chart_range": self.chart_range.rawValue])) + } + } + + func updatePlaylist(updates: JSON) { + let api = PlaylistApi.updatePlaylist(name: self.name, updates: updates) + RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in + switch response.result { + case .success: + break + case .failure: + debugPrint("error: \(self.name), \(updates)") + } + } + //TODO: do better error checking + } private enum CodingKeys: String, CodingKey { case name case uri case username + case type + case include_recommendations case recommendation_sample case include_library_tracks @@ -85,6 +157,13 @@ class Playlist: Identifiable, Equatable, Codable { case lastfm_stat_artist_percent case lastfm_stat_last_refresh + + case add_last_month + case add_this_month + case day_boundary + + case chart_range + case chart_limit } //MARK: Initialization @@ -92,6 +171,8 @@ class Playlist: Identifiable, Equatable, Codable { init(name: String, uri: String = "spotify::", username: String = "NO USER", + + type: String = "default", include_recommendations: Bool = false, recommendation_sample: Int = 0, @@ -115,12 +196,21 @@ class Playlist: Identifiable, Equatable, Codable { lastfm_stat_album_percent: Float = 0, lastfm_stat_artist_percent: Float = 0, - lastfm_stat_last_refresh: String? = ""){ + lastfm_stat_last_refresh: String? = "", + + add_last_month: Bool = false, + add_this_month: Bool = false, + day_boundary: Int = 14, + + chart_range: LastFmRange = .overall, + chart_limit: Int = 10){ self.name = name self.uri = uri self.username = username + self.type = type + self.last_updated = last_updated self.lastfm_stat_count = lastfm_stat_count @@ -144,6 +234,13 @@ class Playlist: Identifiable, Equatable, Codable { self.sort = sort self.description_overwrite = description_overwrite self.description_suffix = description_suffix + + self.add_last_month = add_last_month + self.add_this_month = add_this_month + self.day_boundary = day_boundary + + self.chart_range = chart_range + self.chart_limit = chart_limit } var link: String { @@ -160,11 +257,26 @@ class Playlist: Identifiable, Equatable, Codable { name = try container.decode(String.self, forKey: .name) uri = try container.decode(String.self, forKey: .uri) -// username = try container.decode(String.self, forKey: .username) + do{ + username = try container.decode(String.self, forKey: .username) + }catch { + username = "NO USER" + debugPrint("failed to parse username") + } + + type = try container.decode(String.self, forKey: .type) -// description_overwrite = try container.decode(String.self, forKey: .description_overwrite) -// description_suffix = try container.decode(String.self, forKey: .description_suffix) + do{ + description_overwrite = try container.decode(String.self, forKey: .description_overwrite) + }catch { + debugPrint("no description overwrite") + } + do{ + description_suffix = try container.decode(String.self, forKey: .description_suffix) + }catch { + debugPrint("no description suffix") + } last_updated = try container.decode(String.self, forKey: .last_updated) lastfm_stat_count = try container.decode(Int.self, forKey: .lastfm_stat_count) @@ -179,55 +291,105 @@ class Playlist: Identifiable, Equatable, Codable { include_recommendations = try container.decode(Bool.self, forKey: .include_recommendations) recommendation_sample = try container.decode(Int.self, forKey: .recommendation_sample) - include_library_tracks = try container.decode(Bool.self, forKey: .include_library_tracks) + + do{ + include_library_tracks = try container.decode(Bool.self, forKey: .include_library_tracks) + }catch { + include_library_tracks = false +// debugPrint("failed to parse include_library_tracks") + } parts = try container.decode([String].self, forKey: .parts) playlist_references = try container.decode([String].self, forKey: .playlist_references) shuffle = try container.decode(Bool.self, forKey: .shuffle) - sort = try container.decode(String.self, forKey: .sort) + do{ + sort = try container.decode(String.self, forKey: .sort) + }catch { + sort = "release_date" +// debugPrint("failed to parse sort value") + } + + do { + add_last_month = try container.decode(Bool.self, forKey: .add_last_month) + }catch { + add_last_month = false +// debugPrint("failed to parse add last month") + } + + do { + add_this_month = try container.decode(Bool.self, forKey: .add_this_month) + }catch { + add_this_month = false +// debugPrint("failed to parse add this month") + } + + do { + day_boundary = try container.decode(Int.self, forKey: .day_boundary) + }catch { + day_boundary = 21 +// debugPrint("failed to parse day boundary") + } + + do{ + chart_range = try LastFmRange(rawValue: container.decode(String.self, forKey: .chart_range)) ?? LastFmRange.month + }catch { + chart_range = .halfyear +// debugPrint("failed to parse chart_range") + } + + do{ + chart_limit = try container.decode(Int.self, forKey: .chart_limit) + }catch { + chart_limit = 50 +// debugPrint("failed to parse chart_limit") + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.name, forKey: .name) + try container.encode(self.uri, forKey: .uri) + try container.encode(self.username, forKey: .username) + + try container.encode(self.type, forKey: .type) + + try container.encode(self.include_recommendations, forKey: .include_recommendations) + try container.encode(self.recommendation_sample, forKey: .recommendation_sample) + try container.encode(self.include_library_tracks, forKey: .include_library_tracks) + + try container.encode(self.parts, forKey: .parts) + try container.encode(self.playlist_references, forKey: .playlist_references) + try container.encode(self.shuffle, forKey: .shuffle) + + try container.encode(self.sort, forKey: .sort) + try container.encode(self.description_overwrite, forKey: .description_overwrite) + try container.encode(self.description_suffix, forKey: .description_suffix) + + try container.encode(self.last_updated, forKey: .last_updated) + + try container.encode(self.lastfm_stat_count, forKey: .lastfm_stat_count) + try container.encode(self.lastfm_stat_album_count, forKey: .lastfm_stat_album_count) + try container.encode(self.lastfm_stat_artist_count, forKey: .lastfm_stat_artist_count) + + try container.encode(self.lastfm_stat_percent, forKey: .lastfm_stat_percent) + try container.encode(self.lastfm_stat_album_percent, forKey: .lastfm_stat_album_percent) + try container.encode(self.lastfm_stat_artist_percent, forKey: .lastfm_stat_artist_percent) + + try container.encode(self.lastfm_stat_last_refresh, forKey: .lastfm_stat_last_refresh) + + try container.encode(self.add_last_month, forKey: .add_last_month) + try container.encode(self.add_this_month, forKey: .add_this_month) + try container.encode(self.day_boundary, forKey: .day_boundary) + + try container.encode(self.chart_range, forKey: .chart_range) + try container.encode(self.chart_limit, forKey: .chart_limit) } } -class RecentsPlaylist: Playlist { - - //MARK: Properties - - var add_last_month: Bool - var add_this_month: Bool - var day_boundary: Int - - private enum CodingKeys: String, CodingKey { case add_last_month; case add_this_month; case day_boundary } - - //MARK: Initialization - - init(name: String, - username: String = "NO USER", - - add_last_month: Bool = false, - add_this_month: Bool = false, - day_boundary: Int = 14){ - - self.add_last_month = add_last_month - self.add_this_month = add_this_month - self.day_boundary = day_boundary - - super.init(name: name, username: username) - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - add_last_month = try container.decode(Bool.self, forKey: .add_last_month) - add_this_month = try container.decode(Bool.self, forKey: .add_this_month) - day_boundary = try container.decode(Int.self, forKey: .day_boundary) - - try super.init(from: decoder) - } -} - -enum LastFmRange: String, Decodable { +enum LastFmRange: String, Codable { case overall = "OVERALL" case week = "WEEK" case month = "MONTH" @@ -235,36 +397,3 @@ enum LastFmRange: String, Decodable { case halfyear = "HALFYEAR" case year = "YEAR" } - -class LastFMChartPlaylist: Playlist { - - //MARK: Properties - - var chart_range: LastFmRange - var chart_limit: Int - - private enum CodingKeys: String, CodingKey { case chart_range; case chart_limit } - - //MARK: Initialization - - init(name: String, - username: String = "NO USER", - - chart_range: LastFmRange = .overall, - chart_limit: Int = 10){ - - self.chart_range = chart_range - self.chart_limit = chart_limit - - super.init(name: name, username: username) - } - - required init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - chart_range = try LastFmRange(rawValue: container.decode(String.self, forKey: .chart_range))! - chart_limit = try container.decode(Int.self, forKey: .chart_limit) - - try super.init(from: decoder) - } -} diff --git a/Music Tools/Model/Tag.swift b/Music Tools/Model/Tag.swift index 3f99b3f..5c46240 100644 --- a/Music Tools/Model/Tag.swift +++ b/Music Tools/Model/Tag.swift @@ -15,7 +15,7 @@ class Tag: Identifiable, Equatable, Codable { var tag_id: String var name: String - var username: String +// var username: String var tracks: [JSON] var albums: [JSON] @@ -51,7 +51,7 @@ class Tag: Identifiable, Equatable, Codable { self.tag_id = tag_id self.name = name - self.username = username +// self.username = username self.tracks = tracks self.albums = albums diff --git a/Music Tools/Network/PlaylistApi.swift b/Music Tools/Network/PlaylistApi.swift index 83c4cd5..555559e 100644 --- a/Music Tools/Network/PlaylistApi.swift +++ b/Music Tools/Network/PlaylistApi.swift @@ -123,20 +123,8 @@ extension PlaylistApi: ApiRequest { let decoder = JSONDecoder() do { - let json = try JSON(data: playlist) - switch json["type"].string { - case "default": - let playlist = try decoder.decode(Playlist.self, from: playlist) - return playlist - case "recents": - let playlist = try decoder.decode(RecentsPlaylist.self, from: playlist) - return playlist - case "fmchart": - let playlist = try decoder.decode(LastFMChartPlaylist.self, from: playlist) - return playlist - default: - return nil - } + let playlist = try decoder.decode(Playlist.self, from: playlist) + return playlist } catch { print(error) } @@ -150,19 +138,8 @@ extension PlaylistApi: ApiRequest { if let data = _json { let decoder = JSONDecoder() do { - switch playlist["type"].string { - case "default": - let playlist = try decoder.decode(Playlist.self, from: data) - return playlist - case "recents": - let playlist = try decoder.decode(RecentsPlaylist.self, from: data) - return playlist - case "fmchart": - let playlist = try decoder.decode(LastFMChartPlaylist.self, from: data) - return playlist - default: - return nil - } + let playlist = try decoder.decode(Playlist.self, from: data) + return playlist } catch { print(error) } diff --git a/Music Tools/Views/Playlist/AddPlaylistSheet.swift b/Music Tools/Views/Playlist/AddPlaylistSheet.swift index 7eaec28..5770982 100644 --- a/Music Tools/Views/Playlist/AddPlaylistSheet.swift +++ b/Music Tools/Views/Playlist/AddPlaylistSheet.swift @@ -75,16 +75,16 @@ struct AddPlaylistSheet: View { return } - var playlist: Playlist? = nil + let playlist: Playlist = Playlist(name: name, username: username) switch PlaylistType(rawValue: selectedType) ?? .defaultPlaylist { case .defaultPlaylist: - playlist = Playlist(name: name, username: username) + playlist.type = "default" break case .recents: - playlist = RecentsPlaylist(name: name, username: username) + playlist.type = "recents" break case .fmchart: - playlist = LastFMChartPlaylist(name: name, username: username) + playlist.type = "fmchart" break } @@ -92,7 +92,7 @@ struct AddPlaylistSheet: View { let api = PlaylistApi.newPlaylist(name: self.name, type: PlaylistType(rawValue: selectedType) ?? .defaultPlaylist) RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in - self.playlists.append(playlist!) + self.playlists.append(playlist) self.playlists = self.playlists.sorted(by: { $0.name.lowercased() < $1.name.lowercased() }) self.isLoading = false diff --git a/Music Tools/Views/Playlist/PlaylistView.swift b/Music Tools/Views/Playlist/PlaylistView.swift index abfe80b..6099e89 100644 --- a/Music Tools/Views/Playlist/PlaylistView.swift +++ b/Music Tools/Views/Playlist/PlaylistView.swift @@ -15,11 +15,6 @@ struct PlaylistView: View { @Binding var playlist: Playlist - @State private var this_month: Bool = false - @State private var last_month: Bool = false - @State private var chart_range: LastFmRange = .overall - @State private var chart_limit: Int = 0 - @State private var showingSheet = false @State private var isRefreshing = false @State private var showingNetworkError = false @@ -32,7 +27,7 @@ struct PlaylistView: View { } var body: some View { - List { + Form { Section(header: Text("Stats")){ HStack { Text("Track Count") @@ -104,11 +99,12 @@ struct PlaylistView: View { if self.playlist.include_recommendations { Stepper(onIncrement: { self.$playlist.recommendation_sample.wrappedValue += 1 - self.updatePlaylist(updates: JSON(["recommendation_sample": self.playlist.recommendation_sample])) }, - onDecrement: { - self.$playlist.recommendation_sample.wrappedValue -= 1 - self.updatePlaylist(updates: JSON(["recommendation_sample": self.playlist.recommendation_sample])) + onDecrement: { + if self.playlist.recommendation_sample > 0 { + self.$playlist.recommendation_sample.wrappedValue -= 1 + + } }){ Text("#:") .foregroundColor(Color.gray) @@ -127,24 +123,24 @@ struct PlaylistView: View { Text("Shuffle") } - if playlist is RecentsPlaylist { - Toggle(isOn: $this_month) { + if playlist.type == "recents" { + Toggle(isOn: self.$playlist.add_this_month) { Text("This Month") } - Toggle(isOn: $last_month) { + Toggle(isOn: self.$playlist.add_last_month) { Text("Last Month") } } - if playlist is LastFMChartPlaylist { + if playlist.type == "fmchart" { HStack { Text("Chart Range") Spacer() Button(action: { self.showingSheet = true }) { - Text("\(self.chart_range.rawValue)") + Text("\(self.playlist.chart_range.rawValue)") .foregroundColor(Color.gray) }.actionSheet(isPresented: $showingSheet) { ActionSheet(title: Text("Chart range"), @@ -193,6 +189,7 @@ struct PlaylistView: View { } } + // alert seems to need to be within list root element // else weird crash on half drag back .alert(isPresented: $showingNetworkError) { @@ -200,31 +197,11 @@ struct PlaylistView: View { message: Text("Could not refresh playlist")) } - }.listStyle(GroupedListStyle()) + } + .navigationBarTitle(Text(playlist.name), displayMode: .inline) .pullToRefresh(isShowing: $isRefreshing) { self.refreshPlaylist() } - .navigationBarTitle(Text(playlist.name)) - .onAppear { - - // TODO are these binding properly? - if let playlist = self.playlist as? RecentsPlaylist { - self.$this_month.wrappedValue = playlist.add_this_month - self.$last_month.wrappedValue = playlist.add_last_month - } - - if let playlist = self.playlist as? LastFMChartPlaylist { - self.$chart_range.wrappedValue = playlist.chart_range - self.$chart_limit.wrappedValue = playlist.chart_limit - } - } - } - - func changeChartRange(newRange: LastFmRange) { - self.chart_range = newRange -// self.updatePlaylist(["chart_range": newRange.rawValue]) - //TODO: are enums wrong by the time they're here? not sure api will accept it now - //TODO: fix downcasting local playlist object to change state } func runPlaylist() { diff --git a/Music Tools/Views/Tag/TagView.swift b/Music Tools/Views/Tag/TagView.swift index a4bdecf..ee8c5c8 100644 --- a/Music Tools/Views/Tag/TagView.swift +++ b/Music Tools/Views/Tag/TagView.swift @@ -114,9 +114,6 @@ struct TagView: View { self.refreshTag() } .navigationBarTitle(Text(tag.name)) - .onAppear { - - } } func runTag() {