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 = {
|
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 */,
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
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
|
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: { },
|
||||||
|
@ -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?
|
||||||
|
@ -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))
|
||||||
|
|
||||||
|
@ -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))
|
||||||
|
@ -77,7 +77,7 @@ struct TagView: View {
|
|||||||
Text("Update")
|
Text("Update")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}.listStyle(GroupedListStyle())
|
||||||
.pullToRefresh(isShowing: $isRefreshing) {
|
.pullToRefresh(isShowing: $isRefreshing) {
|
||||||
self.refreshTag()
|
self.refreshTag()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user