diff --git a/Music Tools.xcodeproj/project.pbxproj b/Music Tools.xcodeproj/project.pbxproj index c6e31bb..fbb97bf 100644 --- a/Music Tools.xcodeproj/project.pbxproj +++ b/Music Tools.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + E906F7F42414019C004E1E31 /* NetworkPersister.swift in Sources */ = {isa = PBXBuildFile; fileRef = E906F7F32414019C004E1E31 /* NetworkPersister.swift */; }; E92F94822401412100B6B721 /* SwiftUIRefresh in Frameworks */ = {isa = PBXBuildFile; productRef = E92F94812401412100B6B721 /* SwiftUIRefresh */; }; E934AC99240DD0E4009869F4 /* AddTagSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E934AC98240DD0E4009869F4 /* AddTagSheet.swift */; }; E97AF45623FC4E7800635494 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF45523FC4E7800635494 /* User.swift */; }; @@ -59,6 +60,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + E906F7F32414019C004E1E31 /* NetworkPersister.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkPersister.swift; sourceTree = ""; }; E934AC98240DD0E4009869F4 /* AddTagSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTagSheet.swift; sourceTree = ""; }; E97AF45523FC4E7800635494 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; E97AF45A23FC748D00635494 /* UserApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserApi.swift; sourceTree = ""; }; @@ -150,6 +152,7 @@ E97AF45F23FC85D600635494 /* PlaylistApi.swift */, E97AF45A23FC748D00635494 /* UserApi.swift */, E9E30C2523FEA4EF00574EEF /* TagApi.swift */, + E906F7F32414019C004E1E31 /* NetworkPersister.swift */, ); path = Network; sourceTree = ""; @@ -418,6 +421,7 @@ E9E30C3123FEAF2B00574EEF /* TagObjList.swift in Sources */, E98254CA23FA26600056D9D3 /* PlaylistRow.swift in Sources */, E9EA690B23F9A5430012C3E8 /* AppDelegate.swift in Sources */, + E906F7F42414019C004E1E31 /* NetworkPersister.swift in Sources */, E9E30C3323FF255C00574EEF /* SettingsList.swift in Sources */, E9EA690D23F9A5430012C3E8 /* SceneDelegate.swift in Sources */, E98254DB23FB64740056D9D3 /* Network.swift in Sources */, diff --git a/Music Tools/Model/LiveUser.swift b/Music Tools/Model/LiveUser.swift index aa2d679..7ae0381 100644 --- a/Music Tools/Model/LiveUser.swift +++ b/Music Tools/Model/LiveUser.swift @@ -16,6 +16,9 @@ class LiveUser: ObservableObject { @Published var tags: [Tag] @Published var username: String + @Published var isRefreshingPlaylists = false + @Published var isRefreshingTags = false + init(playlists: [Playlist], tags: [Tag], username: String) { self.playlists = playlists self.tags = tags @@ -30,6 +33,8 @@ class LiveUser: ObservableObject { } func refreshPlaylists() { + self.isRefreshingPlaylists = true + let api = PlaylistApi.getPlaylists RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in @@ -46,6 +51,8 @@ class LiveUser: ObservableObject { // update state self.playlists = PlaylistApi.fromJSON(playlist: playlists).sorted(by: { $0.name.lowercased() < $1.name.lowercased() }) + self.isRefreshingPlaylists = false + let encoder = JSONEncoder() let defaults = UserDefaults.standard do { @@ -57,6 +64,8 @@ class LiveUser: ObservableObject { } func refreshTags() { + self.isRefreshingTags = true + let api = TagApi.getTags RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in @@ -73,6 +82,8 @@ class LiveUser: ObservableObject { // update state self.tags = TagApi.fromJSON(tag: tags).sorted(by: { $0.name.lowercased() < $1.name.lowercased() }) + self.isRefreshingTags = false + let encoder = JSONEncoder() let defaults = UserDefaults.standard do { diff --git a/Music Tools/Model/Playlist.swift b/Music Tools/Model/Playlist.swift index 792d19d..00c3071 100644 --- a/Music Tools/Model/Playlist.swift +++ b/Music Tools/Model/Playlist.swift @@ -37,8 +37,23 @@ class Playlist: Identifiable, Equatable, Codable { var lastfm_stat_artist_count: Int 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 + var lastfm_stat_album_percent_str: String { + get { + return String(format: "%.2f%%", lastfm_stat_album_percent) + } + } 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 diff --git a/Music Tools/Network/NetworkPersister.swift b/Music Tools/Network/NetworkPersister.swift new file mode 100644 index 0000000..f93f502 --- /dev/null +++ b/Music Tools/Network/NetworkPersister.swift @@ -0,0 +1,34 @@ +// +// NetWorkPersister.swift +// Music Tools +// +// Created by Andy Pack on 07/03/2020. +// Copyright © 2020 Sarsoo. All rights reserved. +// + +import Foundation + +@propertyWrapper +struct NetworkPersister: Codable { + + enum ObjectType: String, Codable { + case playlist + case tag + case user + } + + var objType: ObjectType + var key: String +// +// init(_ objType: ObjectType, key: String) { +// self.objType = objType +// self.key = key +// } + + var wrappedValue: Value { + didSet { + print("set") + } + } + +} diff --git a/Music Tools/Views/Playlist/PlaylistInputList.swift b/Music Tools/Views/Playlist/PlaylistInputList.swift index 1723cd9..9fb451c 100644 --- a/Music Tools/Views/Playlist/PlaylistInputList.swift +++ b/Music Tools/Views/Playlist/PlaylistInputList.swift @@ -8,29 +8,39 @@ import SwiftUI -struct Name: Identifiable { +struct Name: Identifiable, Hashable { var id = UUID() var name: String } struct PlaylistInputList: View { - var names: Array = [] + var names: Array = [] var nameType: String init(names: Array, nameType: String){ self.nameType = nameType - self.names = names.map { (name) -> Name in - return Name(name: name) - }.sorted(by: { $0.name.lowercased() < $1.name.lowercased() }) - + self.names = names.sorted(by: { $0.lowercased() < $1.lowercased() }) } var body: some View { - return List(names) { name in - Text(name.name) + List{ + Section(header: Image(systemName: "music.note")){ // Weird? added empty header as list renders with space for header then jumps up, not nice + if self.names.count > 0 { + ForEach(self.names, id: \.self){ name in + Text(name) + } + }else { + HStack { + Text("No Playlists") + .multilineTextAlignment(.center) + Spacer() + } + } + } } - .navigationBarTitle(Text(nameType)) +// .id(UUID()) + .navigationBarTitle(nameType) .navigationBarItems(trailing: Button( action: { }, diff --git a/Music Tools/Views/Playlist/PlaylistView.swift b/Music Tools/Views/Playlist/PlaylistView.swift index 17a3c3c..421c360 100644 --- a/Music Tools/Views/Playlist/PlaylistView.swift +++ b/Music Tools/Views/Playlist/PlaylistView.swift @@ -27,6 +27,38 @@ struct PlaylistView: View { var body: some View { List { + Section(header: Text("Stats")){ + HStack { + Text("Track Count") + Spacer() + Text("\(self.playlist.lastfm_stat_count)") + .font(.title) + .foregroundColor(Color.gray) + Text("\(self.playlist.lastfm_stat_percent_str)") + .font(.body) + .foregroundColor(Color.gray) + } + HStack { + Text("Album Count") + Spacer() + Text("\(self.playlist.lastfm_stat_album_count)") + .font(.title) + .foregroundColor(Color.gray) + Text("\(self.playlist.lastfm_stat_album_percent_str)") + .font(.body) + .foregroundColor(Color.gray) + } + HStack { + Text("Artist Count") + Spacer() + Text("\(self.playlist.lastfm_stat_artist_count)") + .font(.title) + .foregroundColor(Color.gray) + Text("\(self.playlist.lastfm_stat_artist_percent_str)") + .font(.body) + .foregroundColor(Color.gray) + } + } Section(header: Text("Options")){ Toggle(isOn: self.$playlist.include_recommendations) { Text("Spotify Recommendations") @@ -110,7 +142,11 @@ struct PlaylistView: View { } } } - Section(header: Text("Actions")){ + Section(header: Text("Actions"), + footer: VStack(alignment: .leading) { + Text("Last Updated \(self.playlist.last_updated)") + Text("Stats Updated \(self.playlist.lastfm_stat_last_refresh)") + }){ Button(action: { self.runPlaylist() }) { Text("Update") } @@ -119,11 +155,11 @@ struct PlaylistView: View { Text("Open") } } - } + }.listStyle(GroupedListStyle()) .pullToRefresh(isShowing: $isRefreshing) { self.refreshPlaylist() } - .navigationBarTitle(Text(playlist.name)) + .navigationBarTitle(playlist.name) .onAppear { // TODO are these binding properly? diff --git a/Music Tools/Views/RootView.swift b/Music Tools/Views/RootView.swift index c26aa9a..eca3da0 100644 --- a/Music Tools/Views/RootView.swift +++ b/Music Tools/Views/RootView.swift @@ -19,33 +19,37 @@ struct RootView: View { @State private var showAdd = false // State for showing add modal view - @State private var isRefreshingPlaylists = false - @State private var isRefreshingTags = false - var body: some View { TabView { // PLAYLISTS NavigationView { List{ - ForEach(liveUser.playlists.indices, id:\.self) { idx in - PlaylistRow(playlist: self.$liveUser.playlists[idx]) - } - .onDelete { indexSet in - - indexSet.forEach { index in - let api = PlaylistApi.deletePlaylist(name: self.liveUser.playlists[index].name) - RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in - - } + if(liveUser.playlists.count > 0){ + ForEach(liveUser.playlists.indices, id:\.self) { idx in + PlaylistRow(playlist: self.$liveUser.playlists[idx]) + } + .onDelete { indexSet in + + indexSet.forEach { index in + let api = PlaylistApi.deletePlaylist(name: self.liveUser.playlists[index].name) + RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in + + } + } + + self.liveUser.playlists.remove(atOffsets: indexSet) + } + }else { + HStack { + Text("No Playlists") + .multilineTextAlignment(.center) + Spacer() } - - self.liveUser.playlists.remove(atOffsets: indexSet) } } - .pullToRefresh(isShowing: $isRefreshingPlaylists) { + .pullToRefresh(isShowing: self.$liveUser.isRefreshingPlaylists) { self.liveUser.refreshPlaylists() - self.isRefreshingPlaylists = false } .navigationBarTitle(Text("Playlists").font(.title)) @@ -70,24 +74,31 @@ struct RootView: View { // TAGS NavigationView { List{ - ForEach(liveUser.tags.indices, id:\.self) { idx in - TagRow(tag: self.$liveUser.tags[idx]) - } - .onDelete { indexSet in - - indexSet.forEach { index in - let api = TagApi.deleteTag(tag_id: self.liveUser.tags[index].tag_id) - RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in - - } + if(liveUser.tags.count > 0) { + ForEach(liveUser.tags.indices, id:\.self) { idx in + TagRow(tag: self.$liveUser.tags[idx]) + } + .onDelete { indexSet in + + indexSet.forEach { index in + let api = TagApi.deleteTag(tag_id: self.liveUser.tags[index].tag_id) + RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in + + } + } + + self.liveUser.tags.remove(atOffsets: indexSet) + } + } else { + HStack { + Text("No Tags") + .multilineTextAlignment(.center) + Spacer() } - - self.liveUser.tags.remove(atOffsets: indexSet) } } - .pullToRefresh(isShowing: $isRefreshingTags) { + .pullToRefresh(isShowing: self.$liveUser.isRefreshingTags) { self.liveUser.refreshTags() - self.isRefreshingTags = false } .navigationBarTitle(Text("Tags").font(.title)) diff --git a/Music Tools/Views/Tag/TagObjList.swift b/Music Tools/Views/Tag/TagObjList.swift index 78ddeec..b1b557c 100644 --- a/Music Tools/Views/Tag/TagObjList.swift +++ b/Music Tools/Views/Tag/TagObjList.swift @@ -37,20 +37,30 @@ struct TagObjList: View { } var body: some View { - return List(objs) { obj in - HStack { - VStack(alignment: .leading){ - Text(obj.name) -// .multilineTextAlignment(.leading) - if obj.artist.count > 0 { - Text(obj.artist) -// .multilineTextAlignment(.leading) - .foregroundColor(Color.gray) + List { + Section(header: Image(systemName: "music.note")) { + if self.objs.count > 0 { + ForEach(objs) { obj in + HStack { + VStack(alignment: .leading){ + Text(obj.name) + if obj.artist.count > 0 { + Text(obj.artist) + .foregroundColor(Color.gray) + } + } + Spacer() + Text("\(obj.count)") + .foregroundColor(Color.gray) + } + } + }else { + HStack { + Text("No Entries") + .multilineTextAlignment(.center) + Spacer() } } - Spacer() - Text("\(obj.count)") - .foregroundColor(Color.gray) } } .navigationBarTitle(Text(objType)) diff --git a/Music Tools/Views/Tag/TagView.swift b/Music Tools/Views/Tag/TagView.swift index d68da38..e575ed4 100644 --- a/Music Tools/Views/Tag/TagView.swift +++ b/Music Tools/Views/Tag/TagView.swift @@ -77,7 +77,7 @@ struct TagView: View { Text("Update") } } - } + }.listStyle(GroupedListStyle()) .pullToRefresh(isShowing: $isRefreshing) { self.refreshTag() }