added last.fm stats, fixed loading wheel states, added placeholder messages
This commit is contained in:
parent
d012566f04
commit
5d6a5cff1c
@ -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 = "<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>"; };
|
||||
E97AF45A23FC748D00635494 /* UserApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserApi.swift; sourceTree = "<group>"; };
|
||||
@ -150,6 +152,7 @@
|
||||
E97AF45F23FC85D600635494 /* PlaylistApi.swift */,
|
||||
E97AF45A23FC748D00635494 /* UserApi.swift */,
|
||||
E9E30C2523FEA4EF00574EEF /* TagApi.swift */,
|
||||
E906F7F32414019C004E1E31 /* NetworkPersister.swift */,
|
||||
);
|
||||
path = Network;
|
||||
sourceTree = "<group>";
|
||||
@ -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 */,
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
||||
|
34
Music Tools/Network/NetworkPersister.swift
Normal file
34
Music Tools/Network/NetworkPersister.swift
Normal 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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<Name> = []
|
||||
var names: Array<String> = []
|
||||
var nameType: String
|
||||
|
||||
init(names: Array<String>, 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)
|
||||
}
|
||||
.navigationBarTitle(Text(nameType))
|
||||
}else {
|
||||
HStack {
|
||||
Text("No Playlists")
|
||||
.multilineTextAlignment(.center)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// .id(UUID())
|
||||
.navigationBarTitle(nameType)
|
||||
.navigationBarItems(trailing:
|
||||
Button(
|
||||
action: { },
|
||||
|
@ -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?
|
||||
|
@ -19,15 +19,13 @@ 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{
|
||||
if(liveUser.playlists.count > 0){
|
||||
ForEach(liveUser.playlists.indices, id:\.self) { idx in
|
||||
PlaylistRow(playlist: self.$liveUser.playlists[idx])
|
||||
}
|
||||
@ -42,10 +40,16 @@ struct RootView: View {
|
||||
|
||||
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.isRefreshingPlaylists = false
|
||||
}
|
||||
.navigationBarTitle(Text("Playlists").font(.title))
|
||||
|
||||
@ -70,6 +74,7 @@ struct RootView: View {
|
||||
// TAGS
|
||||
NavigationView {
|
||||
List{
|
||||
if(liveUser.tags.count > 0) {
|
||||
ForEach(liveUser.tags.indices, id:\.self) { idx in
|
||||
TagRow(tag: self.$liveUser.tags[idx])
|
||||
}
|
||||
@ -84,10 +89,16 @@ struct RootView: View {
|
||||
|
||||
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.isRefreshingTags = false
|
||||
}
|
||||
.navigationBarTitle(Text("Tags").font(.title))
|
||||
|
||||
|
@ -37,14 +37,15 @@ struct TagObjList: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
return List(objs) { obj in
|
||||
List {
|
||||
Section(header: Image(systemName: "music.note")) {
|
||||
if self.objs.count > 0 {
|
||||
ForEach(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)
|
||||
}
|
||||
}
|
||||
@ -53,6 +54,15 @@ struct TagObjList: View {
|
||||
.foregroundColor(Color.gray)
|
||||
}
|
||||
}
|
||||
}else {
|
||||
HStack {
|
||||
Text("No Entries")
|
||||
.multilineTextAlignment(.center)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationBarTitle(Text(objType))
|
||||
.navigationBarItems(trailing:
|
||||
Button(
|
||||
|
@ -77,7 +77,7 @@ struct TagView: View {
|
||||
Text("Update")
|
||||
}
|
||||
}
|
||||
}
|
||||
}.listStyle(GroupedListStyle())
|
||||
.pullToRefresh(isShowing: $isRefreshing) {
|
||||
self.refreshTag()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user