added last.fm stats, fixed loading wheel states, added placeholder messages

This commit is contained in:
aj 2020-03-07 18:51:52 +00:00
parent d012566f04
commit 5d6a5cff1c
9 changed files with 187 additions and 56 deletions

View File

@ -7,6 +7,7 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
E906F7F42414019C004E1E31 /* NetworkPersister.swift in Sources */ = {isa = PBXBuildFile; fileRef = E906F7F32414019C004E1E31 /* NetworkPersister.swift */; };
E92F94822401412100B6B721 /* SwiftUIRefresh in Frameworks */ = {isa = PBXBuildFile; productRef = E92F94812401412100B6B721 /* SwiftUIRefresh */; }; E92F94822401412100B6B721 /* SwiftUIRefresh in Frameworks */ = {isa = PBXBuildFile; productRef = E92F94812401412100B6B721 /* SwiftUIRefresh */; };
E934AC99240DD0E4009869F4 /* AddTagSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E934AC98240DD0E4009869F4 /* AddTagSheet.swift */; }; E934AC99240DD0E4009869F4 /* AddTagSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E934AC98240DD0E4009869F4 /* AddTagSheet.swift */; };
E97AF45623FC4E7800635494 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF45523FC4E7800635494 /* User.swift */; }; E97AF45623FC4E7800635494 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF45523FC4E7800635494 /* User.swift */; };
@ -59,6 +60,7 @@
/* End PBXContainerItemProxy section */ /* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
E906F7F32414019C004E1E31 /* NetworkPersister.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkPersister.swift; sourceTree = "<group>"; };
E934AC98240DD0E4009869F4 /* AddTagSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTagSheet.swift; sourceTree = "<group>"; }; E934AC98240DD0E4009869F4 /* AddTagSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTagSheet.swift; sourceTree = "<group>"; };
E97AF45523FC4E7800635494 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; }; E97AF45523FC4E7800635494 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
E97AF45A23FC748D00635494 /* UserApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserApi.swift; sourceTree = "<group>"; }; E97AF45A23FC748D00635494 /* UserApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserApi.swift; sourceTree = "<group>"; };
@ -150,6 +152,7 @@
E97AF45F23FC85D600635494 /* PlaylistApi.swift */, E97AF45F23FC85D600635494 /* PlaylistApi.swift */,
E97AF45A23FC748D00635494 /* UserApi.swift */, E97AF45A23FC748D00635494 /* UserApi.swift */,
E9E30C2523FEA4EF00574EEF /* TagApi.swift */, E9E30C2523FEA4EF00574EEF /* TagApi.swift */,
E906F7F32414019C004E1E31 /* NetworkPersister.swift */,
); );
path = Network; path = Network;
sourceTree = "<group>"; sourceTree = "<group>";
@ -418,6 +421,7 @@
E9E30C3123FEAF2B00574EEF /* TagObjList.swift in Sources */, E9E30C3123FEAF2B00574EEF /* TagObjList.swift in Sources */,
E98254CA23FA26600056D9D3 /* PlaylistRow.swift in Sources */, E98254CA23FA26600056D9D3 /* PlaylistRow.swift in Sources */,
E9EA690B23F9A5430012C3E8 /* AppDelegate.swift in Sources */, E9EA690B23F9A5430012C3E8 /* AppDelegate.swift in Sources */,
E906F7F42414019C004E1E31 /* NetworkPersister.swift in Sources */,
E9E30C3323FF255C00574EEF /* SettingsList.swift in Sources */, E9E30C3323FF255C00574EEF /* SettingsList.swift in Sources */,
E9EA690D23F9A5430012C3E8 /* SceneDelegate.swift in Sources */, E9EA690D23F9A5430012C3E8 /* SceneDelegate.swift in Sources */,
E98254DB23FB64740056D9D3 /* Network.swift in Sources */, E98254DB23FB64740056D9D3 /* Network.swift in Sources */,

View File

@ -16,6 +16,9 @@ class LiveUser: ObservableObject {
@Published var tags: [Tag] @Published var tags: [Tag]
@Published var username: String @Published var username: String
@Published var isRefreshingPlaylists = false
@Published var isRefreshingTags = false
init(playlists: [Playlist], tags: [Tag], username: String) { init(playlists: [Playlist], tags: [Tag], username: String) {
self.playlists = playlists self.playlists = playlists
self.tags = tags self.tags = tags
@ -30,6 +33,8 @@ class LiveUser: ObservableObject {
} }
func refreshPlaylists() { func refreshPlaylists() {
self.isRefreshingPlaylists = true
let api = PlaylistApi.getPlaylists let api = PlaylistApi.getPlaylists
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
@ -46,6 +51,8 @@ class LiveUser: ObservableObject {
// update state // update state
self.playlists = PlaylistApi.fromJSON(playlist: playlists).sorted(by: { $0.name.lowercased() < $1.name.lowercased() }) self.playlists = PlaylistApi.fromJSON(playlist: playlists).sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
self.isRefreshingPlaylists = false
let encoder = JSONEncoder() let encoder = JSONEncoder()
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
do { do {
@ -57,6 +64,8 @@ class LiveUser: ObservableObject {
} }
func refreshTags() { func refreshTags() {
self.isRefreshingTags = true
let api = TagApi.getTags let api = TagApi.getTags
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
@ -73,6 +82,8 @@ class LiveUser: ObservableObject {
// update state // update state
self.tags = TagApi.fromJSON(tag: tags).sorted(by: { $0.name.lowercased() < $1.name.lowercased() }) self.tags = TagApi.fromJSON(tag: tags).sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
self.isRefreshingTags = false
let encoder = JSONEncoder() let encoder = JSONEncoder()
let defaults = UserDefaults.standard let defaults = UserDefaults.standard
do { do {

View File

@ -37,8 +37,23 @@ class Playlist: Identifiable, Equatable, Codable {
var lastfm_stat_artist_count: Int var lastfm_stat_artist_count: Int
var lastfm_stat_percent: Float 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: 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: Float
var lastfm_stat_artist_percent_str: String {
get {
return String(format: "%.2f%%", lastfm_stat_artist_percent)
}
}
var lastfm_stat_last_refresh: String var lastfm_stat_last_refresh: String

View File

@ -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<Value: Codable>: 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")
}
}
}

View File

@ -8,29 +8,39 @@
import SwiftUI import SwiftUI
struct Name: Identifiable { struct Name: Identifiable, Hashable {
var id = UUID() var id = UUID()
var name: String var name: String
} }
struct PlaylistInputList: View { struct PlaylistInputList: View {
var names: Array<Name> = [] var names: Array<String> = []
var nameType: String var nameType: String
init(names: Array<String>, nameType: String){ init(names: Array<String>, nameType: String){
self.nameType = nameType self.nameType = nameType
self.names = names.map { (name) -> Name in self.names = names.sorted(by: { $0.lowercased() < $1.lowercased() })
return Name(name: name)
}.sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
} }
var body: some View { var body: some View {
return List(names) { name in List{
Text(name.name) 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: .navigationBarItems(trailing:
Button( Button(
action: { }, action: { },

View File

@ -27,6 +27,38 @@ struct PlaylistView: View {
var body: some View { var body: some View {
List { 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")){ Section(header: Text("Options")){
Toggle(isOn: self.$playlist.include_recommendations) { Toggle(isOn: self.$playlist.include_recommendations) {
Text("Spotify 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() }) { Button(action: { self.runPlaylist() }) {
Text("Update") Text("Update")
} }
@ -119,11 +155,11 @@ struct PlaylistView: View {
Text("Open") Text("Open")
} }
} }
} }.listStyle(GroupedListStyle())
.pullToRefresh(isShowing: $isRefreshing) { .pullToRefresh(isShowing: $isRefreshing) {
self.refreshPlaylist() self.refreshPlaylist()
} }
.navigationBarTitle(Text(playlist.name)) .navigationBarTitle(playlist.name)
.onAppear { .onAppear {
// TODO are these binding properly? // TODO are these binding properly?

View File

@ -19,33 +19,37 @@ struct RootView: View {
@State private var showAdd = false // State for showing add modal 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 { var body: some View {
TabView { TabView {
// PLAYLISTS // PLAYLISTS
NavigationView { NavigationView {
List{ List{
ForEach(liveUser.playlists.indices, id:\.self) { idx in if(liveUser.playlists.count > 0){
PlaylistRow(playlist: self.$liveUser.playlists[idx]) 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
}
} }
.onDelete { indexSet in
self.liveUser.playlists.remove(atOffsets: indexSet) 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()
}
} }
} }
.pullToRefresh(isShowing: $isRefreshingPlaylists) { .pullToRefresh(isShowing: self.$liveUser.isRefreshingPlaylists) {
self.liveUser.refreshPlaylists() self.liveUser.refreshPlaylists()
self.isRefreshingPlaylists = false
} }
.navigationBarTitle(Text("Playlists").font(.title)) .navigationBarTitle(Text("Playlists").font(.title))
@ -70,24 +74,31 @@ struct RootView: View {
// TAGS // TAGS
NavigationView { NavigationView {
List{ List{
ForEach(liveUser.tags.indices, id:\.self) { idx in if(liveUser.tags.count > 0) {
TagRow(tag: self.$liveUser.tags[idx]) 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
}
} }
.onDelete { indexSet in
self.liveUser.tags.remove(atOffsets: indexSet) 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()
}
} }
} }
.pullToRefresh(isShowing: $isRefreshingTags) { .pullToRefresh(isShowing: self.$liveUser.isRefreshingTags) {
self.liveUser.refreshTags() self.liveUser.refreshTags()
self.isRefreshingTags = false
} }
.navigationBarTitle(Text("Tags").font(.title)) .navigationBarTitle(Text("Tags").font(.title))

View File

@ -37,20 +37,30 @@ struct TagObjList: View {
} }
var body: some View { var body: some View {
return List(objs) { obj in List {
HStack { Section(header: Image(systemName: "music.note")) {
VStack(alignment: .leading){ if self.objs.count > 0 {
Text(obj.name) ForEach(objs) { obj in
// .multilineTextAlignment(.leading) HStack {
if obj.artist.count > 0 { VStack(alignment: .leading){
Text(obj.artist) Text(obj.name)
// .multilineTextAlignment(.leading) if obj.artist.count > 0 {
.foregroundColor(Color.gray) 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)) .navigationBarTitle(Text(objType))

View File

@ -77,7 +77,7 @@ struct TagView: View {
Text("Update") Text("Update")
} }
} }
} }.listStyle(GroupedListStyle())
.pullToRefresh(isShowing: $isRefreshing) { .pullToRefresh(isShowing: $isRefreshing) {
self.refreshTag() self.refreshTag()
} }