flattened model into one playlist, added network functions on playlist change

This commit is contained in:
aj 2020-04-30 17:05:23 +01:00
parent 228fdb3991
commit 3a68d3c892
6 changed files with 252 additions and 172 deletions

View File

@ -10,58 +10,130 @@ import Foundation
import UIKit import UIKit
import SwiftyJSON import SwiftyJSON
class Playlist: Identifiable, Equatable, Codable { class Playlist: Identifiable, Equatable, Codable, ObservableObject {
//MARK: Properties //MARK: Properties
var name: String @Published var name: String
var uri: String @Published var uri: String
var username: String? @Published var username: String?
var include_recommendations: Bool @Published var type: String {
var recommendation_sample: Int didSet {
var include_library_tracks: Bool self.updatePlaylist(updates: JSON(["type": self.type]))
}
}
var parts: Array<String> @Published var include_recommendations: Bool {
var playlist_references: Array<String> didSet {
var shuffle: Bool 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 @Published var parts: Array<String>{
var description_overwrite: String? didSet {
var description_suffix: String? self.updatePlaylist(updates: JSON(["parts": self.parts]))
}
}
@Published var playlist_references: Array<String>{
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 @Published var last_updated: String?
var lastfm_stat_album_count: Int
var lastfm_stat_artist_count: Int
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 { var lastfm_stat_percent_str: String {
get { get {
return String(format: "%.2f%%", lastfm_stat_percent) 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 { var lastfm_stat_album_percent_str: String {
get { get {
return String(format: "%.2f%%", lastfm_stat_album_percent) 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 { var lastfm_stat_artist_percent_str: String {
get { get {
return String(format: "%.2f%%", lastfm_stat_artist_percent) 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 { private enum CodingKeys: String, CodingKey {
case name case name
case uri case uri
case username case username
case type
case include_recommendations case include_recommendations
case recommendation_sample case recommendation_sample
case include_library_tracks case include_library_tracks
@ -85,6 +157,13 @@ class Playlist: Identifiable, Equatable, Codable {
case lastfm_stat_artist_percent case lastfm_stat_artist_percent
case lastfm_stat_last_refresh case lastfm_stat_last_refresh
case add_last_month
case add_this_month
case day_boundary
case chart_range
case chart_limit
} }
//MARK: Initialization //MARK: Initialization
@ -92,6 +171,8 @@ class Playlist: Identifiable, Equatable, Codable {
init(name: String, init(name: String,
uri: String = "spotify::", uri: String = "spotify::",
username: String = "NO USER", username: String = "NO USER",
type: String = "default",
include_recommendations: Bool = false, include_recommendations: Bool = false,
recommendation_sample: Int = 0, recommendation_sample: Int = 0,
@ -115,12 +196,21 @@ class Playlist: Identifiable, Equatable, Codable {
lastfm_stat_album_percent: Float = 0, lastfm_stat_album_percent: Float = 0,
lastfm_stat_artist_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.name = name
self.uri = uri self.uri = uri
self.username = username self.username = username
self.type = type
self.last_updated = last_updated self.last_updated = last_updated
self.lastfm_stat_count = lastfm_stat_count self.lastfm_stat_count = lastfm_stat_count
@ -144,6 +234,13 @@ class Playlist: Identifiable, Equatable, Codable {
self.sort = sort self.sort = sort
self.description_overwrite = description_overwrite self.description_overwrite = description_overwrite
self.description_suffix = description_suffix 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 { var link: String {
@ -160,11 +257,26 @@ class Playlist: Identifiable, Equatable, Codable {
name = try container.decode(String.self, forKey: .name) name = try container.decode(String.self, forKey: .name)
uri = try container.decode(String.self, forKey: .uri) 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) do{
// description_suffix = try container.decode(String.self, forKey: .description_suffix) 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) last_updated = try container.decode(String.self, forKey: .last_updated)
lastfm_stat_count = try container.decode(Int.self, forKey: .lastfm_stat_count) 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) include_recommendations = try container.decode(Bool.self, forKey: .include_recommendations)
recommendation_sample = try container.decode(Int.self, forKey: .recommendation_sample) 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) parts = try container.decode([String].self, forKey: .parts)
playlist_references = try container.decode([String].self, forKey: .playlist_references) playlist_references = try container.decode([String].self, forKey: .playlist_references)
shuffle = try container.decode(Bool.self, forKey: .shuffle) 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 { enum LastFmRange: String, Codable {
//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 {
case overall = "OVERALL" case overall = "OVERALL"
case week = "WEEK" case week = "WEEK"
case month = "MONTH" case month = "MONTH"
@ -235,36 +397,3 @@ enum LastFmRange: String, Decodable {
case halfyear = "HALFYEAR" case halfyear = "HALFYEAR"
case year = "YEAR" 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)
}
}

View File

@ -15,7 +15,7 @@ class Tag: Identifiable, Equatable, Codable {
var tag_id: String var tag_id: String
var name: String var name: String
var username: String // var username: String
var tracks: [JSON] var tracks: [JSON]
var albums: [JSON] var albums: [JSON]
@ -51,7 +51,7 @@ class Tag: Identifiable, Equatable, Codable {
self.tag_id = tag_id self.tag_id = tag_id
self.name = name self.name = name
self.username = username // self.username = username
self.tracks = tracks self.tracks = tracks
self.albums = albums self.albums = albums

View File

@ -123,20 +123,8 @@ extension PlaylistApi: ApiRequest {
let decoder = JSONDecoder() let decoder = JSONDecoder()
do { do {
let json = try JSON(data: playlist) let playlist = try decoder.decode(Playlist.self, from: playlist)
switch json["type"].string { return playlist
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
}
} catch { } catch {
print(error) print(error)
} }
@ -150,19 +138,8 @@ extension PlaylistApi: ApiRequest {
if let data = _json { if let data = _json {
let decoder = JSONDecoder() let decoder = JSONDecoder()
do { do {
switch playlist["type"].string { let playlist = try decoder.decode(Playlist.self, from: data)
case "default": return playlist
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
}
} catch { } catch {
print(error) print(error)
} }

View File

@ -75,16 +75,16 @@ struct AddPlaylistSheet: View {
return return
} }
var playlist: Playlist? = nil let playlist: Playlist = Playlist(name: name, username: username)
switch PlaylistType(rawValue: selectedType) ?? .defaultPlaylist { switch PlaylistType(rawValue: selectedType) ?? .defaultPlaylist {
case .defaultPlaylist: case .defaultPlaylist:
playlist = Playlist(name: name, username: username) playlist.type = "default"
break break
case .recents: case .recents:
playlist = RecentsPlaylist(name: name, username: username) playlist.type = "recents"
break break
case .fmchart: case .fmchart:
playlist = LastFMChartPlaylist(name: name, username: username) playlist.type = "fmchart"
break break
} }
@ -92,7 +92,7 @@ struct AddPlaylistSheet: View {
let api = PlaylistApi.newPlaylist(name: self.name, let api = PlaylistApi.newPlaylist(name: self.name,
type: PlaylistType(rawValue: selectedType) ?? .defaultPlaylist) type: PlaylistType(rawValue: selectedType) ?? .defaultPlaylist)
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in 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.playlists = self.playlists.sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
self.isLoading = false self.isLoading = false

View File

@ -15,11 +15,6 @@ struct PlaylistView: View {
@Binding var playlist: Playlist @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 showingSheet = false
@State private var isRefreshing = false @State private var isRefreshing = false
@State private var showingNetworkError = false @State private var showingNetworkError = false
@ -32,7 +27,7 @@ struct PlaylistView: View {
} }
var body: some View { var body: some View {
List { Form {
Section(header: Text("Stats")){ Section(header: Text("Stats")){
HStack { HStack {
Text("Track Count") Text("Track Count")
@ -104,11 +99,12 @@ struct PlaylistView: View {
if self.playlist.include_recommendations { if self.playlist.include_recommendations {
Stepper(onIncrement: { Stepper(onIncrement: {
self.$playlist.recommendation_sample.wrappedValue += 1 self.$playlist.recommendation_sample.wrappedValue += 1
self.updatePlaylist(updates: JSON(["recommendation_sample": self.playlist.recommendation_sample]))
}, },
onDecrement: { onDecrement: {
self.$playlist.recommendation_sample.wrappedValue -= 1 if self.playlist.recommendation_sample > 0 {
self.updatePlaylist(updates: JSON(["recommendation_sample": self.playlist.recommendation_sample])) self.$playlist.recommendation_sample.wrappedValue -= 1
}
}){ }){
Text("#:") Text("#:")
.foregroundColor(Color.gray) .foregroundColor(Color.gray)
@ -127,24 +123,24 @@ struct PlaylistView: View {
Text("Shuffle") Text("Shuffle")
} }
if playlist is RecentsPlaylist { if playlist.type == "recents" {
Toggle(isOn: $this_month) { Toggle(isOn: self.$playlist.add_this_month) {
Text("This Month") Text("This Month")
} }
Toggle(isOn: $last_month) { Toggle(isOn: self.$playlist.add_last_month) {
Text("Last Month") Text("Last Month")
} }
} }
if playlist is LastFMChartPlaylist { if playlist.type == "fmchart" {
HStack { HStack {
Text("Chart Range") Text("Chart Range")
Spacer() Spacer()
Button(action: { Button(action: {
self.showingSheet = true self.showingSheet = true
}) { }) {
Text("\(self.chart_range.rawValue)") Text("\(self.playlist.chart_range.rawValue)")
.foregroundColor(Color.gray) .foregroundColor(Color.gray)
}.actionSheet(isPresented: $showingSheet) { }.actionSheet(isPresented: $showingSheet) {
ActionSheet(title: Text("Chart range"), ActionSheet(title: Text("Chart range"),
@ -193,6 +189,7 @@ struct PlaylistView: View {
} }
} }
// alert seems to need to be within list root element // alert seems to need to be within list root element
// else weird crash on half drag back // else weird crash on half drag back
.alert(isPresented: $showingNetworkError) { .alert(isPresented: $showingNetworkError) {
@ -200,31 +197,11 @@ struct PlaylistView: View {
message: Text("Could not refresh playlist")) message: Text("Could not refresh playlist"))
} }
}.listStyle(GroupedListStyle()) }
.navigationBarTitle(Text(playlist.name), displayMode: .inline)
.pullToRefresh(isShowing: $isRefreshing) { .pullToRefresh(isShowing: $isRefreshing) {
self.refreshPlaylist() 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() { func runPlaylist() {

View File

@ -114,9 +114,6 @@ struct TagView: View {
self.refreshTag() self.refreshTag()
} }
.navigationBarTitle(Text(tag.name)) .navigationBarTitle(Text(tag.name))
.onAppear {
}
} }
func runTag() { func runTag() {