diff --git a/Music Tools.xcodeproj/project.pbxproj b/Music Tools.xcodeproj/project.pbxproj index be7a74b..3a07189 100644 --- a/Music Tools.xcodeproj/project.pbxproj +++ b/Music Tools.xcodeproj/project.pbxproj @@ -26,6 +26,8 @@ E98254D023FB00B60056D9D3 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98254CF23FB00B60056D9D3 /* LoginScreen.swift */; }; E98254D923FB53780056D9D3 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = E98254D823FB53780056D9D3 /* Alamofire */; }; E98254DB23FB64740056D9D3 /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98254DA23FB64740056D9D3 /* Network.swift */; }; + E9CCD5BB2454C57300B5CD6C /* PlaylistList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9CCD5BA2454C57300B5CD6C /* PlaylistList.swift */; }; + E9CCD5BD2454C64300B5CD6C /* TagList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9CCD5BC2454C64300B5CD6C /* TagList.swift */; }; E9E30C2623FEA4F000574EEF /* TagApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E30C2523FEA4EF00574EEF /* TagApi.swift */; }; E9E30C2823FEA6BD00574EEF /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E30C2723FEA6BD00574EEF /* Tag.swift */; }; E9E30C2A23FEAA3A00574EEF /* TagRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E30C2923FEAA3A00574EEF /* TagRow.swift */; }; @@ -74,6 +76,8 @@ E98254C923FA26600056D9D3 /* PlaylistRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistRow.swift; sourceTree = ""; }; E98254CF23FB00B60056D9D3 /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreen.swift; sourceTree = ""; }; E98254DA23FB64740056D9D3 /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; }; + E9CCD5BA2454C57300B5CD6C /* PlaylistList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistList.swift; sourceTree = ""; }; + E9CCD5BC2454C64300B5CD6C /* TagList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagList.swift; sourceTree = ""; }; E9E30C2523FEA4EF00574EEF /* TagApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagApi.swift; sourceTree = ""; }; E9E30C2723FEA6BD00574EEF /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = ""; }; E9E30C2923FEAA3A00574EEF /* TagRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagRow.swift; sourceTree = ""; }; @@ -178,6 +182,7 @@ E98254C923FA26600056D9D3 /* PlaylistRow.swift */, E97AF46623FD650800635494 /* AddPlaylistSheet.swift */, E97AF46823FD9E1B00635494 /* PlaylistInputList.swift */, + E9CCD5BA2454C57300B5CD6C /* PlaylistList.swift */, ); path = Playlist; sourceTree = ""; @@ -189,6 +194,7 @@ E9E30C2C23FEAB0200574EEF /* TagView.swift */, E9E30C3023FEAF2B00574EEF /* TagObjList.swift */, E934AC98240DD0E4009869F4 /* AddTagSheet.swift */, + E9CCD5BC2454C64300B5CD6C /* TagList.swift */, ); path = Tag; sourceTree = ""; @@ -417,7 +423,9 @@ E9E30C3323FF255C00574EEF /* SettingsList.swift in Sources */, E9EA690D23F9A5430012C3E8 /* SceneDelegate.swift in Sources */, E98254DB23FB64740056D9D3 /* Network.swift in Sources */, + E9CCD5BD2454C64300B5CD6C /* TagList.swift in Sources */, E9E30C2A23FEAA3A00574EEF /* TagRow.swift in Sources */, + E9CCD5BB2454C57300B5CD6C /* PlaylistList.swift in Sources */, E97AF46023FC85D600635494 /* PlaylistApi.swift in Sources */, E9EA690F23F9A5430012C3E8 /* AppSkeleton.swift in Sources */, E98254BD23F9B7A90056D9D3 /* Playlist.swift in Sources */, diff --git a/Music Tools/Application/AppDelegate.swift b/Music Tools/Application/AppDelegate.swift index 98bc39e..6217415 100644 --- a/Music Tools/Application/AppDelegate.swift +++ b/Music Tools/Application/AppDelegate.swift @@ -15,15 +15,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. - - let keychain = Keychain(service: "xyz.sarsoo.music.login") -// do { -// try keychain.remove("username") -// try keychain.remove("password") -// } catch let error { -// debugPrint("Could not clear keychain, \(error)") -// } -// return true } diff --git a/Music Tools/Application/SceneDelegate.swift b/Music Tools/Application/SceneDelegate.swift index 68997fc..81bbda8 100644 --- a/Music Tools/Application/SceneDelegate.swift +++ b/Music Tools/Application/SceneDelegate.swift @@ -27,13 +27,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Use a UIHostingController as window root view controller. if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) - let liveUser = LiveUser(playlists: [], tags: [], username: keychain["username"] ?? "", loggedIn: false).loadUserDefaults() - window.rootViewController = UIHostingController(rootView: contentView.environmentObject(liveUser)) -// window.rootViewController = UIHostingController(rootView: contentView.environmentObject(liveUser)) -// window.rootViewController = LoginController() self.window = window window.makeKeyAndVisible() } @@ -66,7 +62,5 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Use this method to save data, release shared resources, and store enough scene-specific state information // to restore the scene back to its current state. } - - } diff --git a/Music Tools/Router.swift b/Music Tools/Router.swift index baef478..646e3e8 100644 --- a/Music Tools/Router.swift +++ b/Music Tools/Router.swift @@ -8,6 +8,7 @@ import SwiftUI +// Root level view loaded by delegate to show either main app or login screen struct Router: View { @EnvironmentObject var liveUser: LiveUser diff --git a/Music Tools/Views/AppSkeleton.swift b/Music Tools/Views/AppSkeleton.swift index de5278b..1223162 100644 --- a/Music Tools/Views/AppSkeleton.swift +++ b/Music Tools/Views/AppSkeleton.swift @@ -16,120 +16,37 @@ struct AppSkeleton: View { @EnvironmentObject var liveUser: LiveUser @State private var selection = 0 // Tab view selection - @State private var showAdd = false // State for showing add modal view 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]) - } - .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() - } + PlaylistList() + .tabItem { + VStack { + Image(systemName: "music.note.list") + Text("Playlists") } } - .pullToRefresh(isShowing: self.$liveUser.isRefreshingPlaylists) { - self.liveUser.refreshPlaylists() - } - .navigationBarTitle(Text("Playlists").font(.title)) - - // add playlist button - .navigationBarItems(trailing: - Button( - action: { self.showAdd = true }, - label: { Text("Add") } - ).sheet(isPresented: $showAdd) { - AddPlaylistSheet(playlists: self.$liveUser.playlists, username: self.$liveUser.username) - } - ) - } - .tabItem { - VStack { - Image(systemName: "music.note.list") - Text("Playlists") - } - } - .tag(0) + .tag(0) - // TAGS - NavigationView { - List{ - 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() - } + TagList() + .tabItem { + VStack { + Image(systemName: "tag") + Text("Tags") } } - .pullToRefresh(isShowing: self.$liveUser.isRefreshingTags) { - self.liveUser.refreshTags() - } - .navigationBarTitle(Text("Tags").font(.title)) - - // add playlist button - .navigationBarItems(trailing: - Button( - action: { self.showAdd = true }, - label: { Text("Add") } - ).sheet(isPresented: $showAdd) { - AddTagSheet(tags: self.$liveUser.tags, username: self.$liveUser.username) - } - ) - } - .tabItem { - VStack { - Image(systemName: "tag") - Text("Tags") - } - } - .tag(1) + .tag(1) - // SETTINGS - NavigationView { - SettingsList() - .navigationBarTitle(Text("Settings").font(.title)) - }.tabItem { - VStack { - Image(systemName: "slider.horizontal.3") - Text("Settings") + SettingsList() + .tabItem { + VStack { + Image(systemName: "slider.horizontal.3") + Text("Settings") + } } - } - .tag(2) + .tag(2) + }.onAppear { self.fetchAll() } diff --git a/Music Tools/Views/Playlist/PlaylistList.swift b/Music Tools/Views/Playlist/PlaylistList.swift new file mode 100644 index 0000000..ccedb57 --- /dev/null +++ b/Music Tools/Views/Playlist/PlaylistList.swift @@ -0,0 +1,58 @@ +// +// PlaylistList.swift +// Music Tools +// +// Created by Andy Pack on 25/04/2020. +// Copyright © 2020 Sarsoo. All rights reserved. +// + +import SwiftUI + +struct PlaylistList: View { + + @EnvironmentObject var liveUser: LiveUser + @State private var showAdd = false // State for showing add modal view + + var body: some View { + NavigationView { + List{ + 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 { + Text("No Playlists") + } + } + .pullToRefresh(isShowing: self.$liveUser.isRefreshingPlaylists) { + self.liveUser.refreshPlaylists() + } + .navigationBarTitle(Text("Playlists 📻")) + .navigationBarItems(trailing: + Button( + action: { self.showAdd = true }, + label: { Text("Add") } + ).sheet(isPresented: $showAdd) { + AddPlaylistSheet(playlists: self.$liveUser.playlists, username: self.$liveUser.username) + } + ) + } + } +} + +struct PlaylistList_Previews: PreviewProvider { + static var previews: some View { + PlaylistList() + } +} diff --git a/Music Tools/Views/Playlist/PlaylistRow.swift b/Music Tools/Views/Playlist/PlaylistRow.swift index c53a4a4..c9cc82f 100644 --- a/Music Tools/Views/Playlist/PlaylistRow.swift +++ b/Music Tools/Views/Playlist/PlaylistRow.swift @@ -10,35 +10,47 @@ import SwiftUI import SwiftyJSON struct PlaylistRow: View { + @Binding var playlist: Playlist + @State private var showingNetworkError = false var body: some View { NavigationLink(destination: PlaylistView(playlist: $playlist)){ HStack { Text(playlist.name) - .contextMenu { - - // run force touch - Button(action: { - let api = PlaylistApi.runPlaylist(name: self.playlist.name) - RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in - - } - }) { - Text("Refresh") - Image(systemName: "arrow.clockwise.circle") - } - - // open force touch - Button(action: { - if let url = URL(string: self.playlist.link) { - UIApplication.shared.open(url) - } - }) { - Text("Open") - Image(systemName: "arrowshape.turn.up.right.circle") - } + if playlist.lastfm_stat_count > 0 { + Spacer() + Text("\(playlist.lastfm_stat_count)") + .foregroundColor(.gray) } + }.contextMenu { + Button(action: { + let api = PlaylistApi.runPlaylist(name: self.playlist.name) + RequestBuilder.buildRequest(apiRequest: api) + .validate() + .responseJSON{ response in + switch response.result { + case .success: + break + case .failure: + self.showingNetworkError = true + } + } + }) { + Text("Refresh") + Image(systemName: "arrow.clockwise.circle") + } + Button(action: { + if let url = URL(string: self.playlist.link) { + UIApplication.shared.open(url) + } + }) { + Text("Open") + Image(systemName: "arrowshape.turn.up.right.circle") + } + }.alert(isPresented: $showingNetworkError) { + Alert(title: Text("Network Error"), + message: Text("Could not refresh playlist")) } } } diff --git a/Music Tools/Views/Playlist/PlaylistView.swift b/Music Tools/Views/Playlist/PlaylistView.swift index 17bce93..22133ba 100644 --- a/Music Tools/Views/Playlist/PlaylistView.swift +++ b/Music Tools/Views/Playlist/PlaylistView.swift @@ -24,6 +24,7 @@ struct PlaylistView: View { @State private var showingSheet = false @State private var isRefreshing = false + @State private var showingNetworkError = false var chartStyle: ChartStyle { get { @@ -211,6 +212,10 @@ struct PlaylistView: View { self.$chart_limit.wrappedValue = playlist.chart_limit } } + .alert(isPresented: $showingNetworkError) { + Alert(title: Text("Network Error"), + message: Text("Could not refresh playlist")) + } } func changeChartRange(newRange: LastFmRange) { @@ -222,8 +227,15 @@ struct PlaylistView: View { func runPlaylist() { let api = PlaylistApi.runPlaylist(name: playlist.name) - RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in - + RequestBuilder.buildRequest(apiRequest: api) + .validate() + .responseJSON{ response in + switch response.result { + case .success: + break + case .failure: + self.showingNetworkError = true + } } //TODO: do better error checking } diff --git a/Music Tools/Views/Settings/SettingsList.swift b/Music Tools/Views/Settings/SettingsList.swift index 6644876..d79f23c 100644 --- a/Music Tools/Views/Settings/SettingsList.swift +++ b/Music Tools/Views/Settings/SettingsList.swift @@ -13,65 +13,63 @@ struct SettingsList: View { @EnvironmentObject var liveUser: LiveUser - init(){ - UITableView.appearance().tableFooterView = UIView() - } - var body: some View { - VStack{ - List{ - Section { - Button(action: { - if let url = URL(string: "https://music.sarsoo.xyz") { - UIApplication.shared.open(url) + NavigationView { + List{ + Section { + Button(action: { + if let url = URL(string: "https://music.sarsoo.xyz") { + UIApplication.shared.open(url) + } + }) { + Text("Launch Web Version") + } + Button(action: { + let keychain = Keychain(service: "xyz.sarsoo.music.login") + do { + try keychain.remove("username") + try keychain.remove("password") + + self.liveUser.loggedIn = false + + } catch let error { + debugPrint("Could not clear keychain, \(error)") + } + }) { + Text("Log out") } - }) { - Text("Launch Web Version") } - Button(action: { - let keychain = Keychain(service: "xyz.sarsoo.music.login") - do { - try keychain.remove("username") - try keychain.remove("password") - - self.liveUser.loggedIn = false - - } catch let error { - debugPrint("Could not clear keychain, \(error)") + Section( + header: + Text("Development"), + footer: + HStack{ + Spacer() + Image("APFooter") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 100.0) + Spacer() + } + ) { + Button(action: { + if let url = URL(string: "https://github.com/sarsoo/music-tools") { + UIApplication.shared.open(url) + } + }) { + Text("Server Source") + } + Button(action: { + if let url = URL(string: "https://github.com/sarsoo/music-tools-ios") { + UIApplication.shared.open(url) + } + }) { + Text("iOS Source") } - }) { - Text("Log out") } } - Section( - header: - Text("Development"), - footer: - HStack{ - Spacer() - Image("APFooter") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 100.0) - Spacer() - } - ) { - Button(action: { - if let url = URL(string: "https://github.com/sarsoo/music-tools") { - UIApplication.shared.open(url) - } - }) { - Text("Server Source") - } - Button(action: { - if let url = URL(string: "https://github.com/sarsoo/music-tools-ios") { - UIApplication.shared.open(url) - } - }) { - Text("iOS Source") - } - } - }.listStyle(GroupedListStyle()) + .listStyle(GroupedListStyle()) + .navigationBarTitle(Text("Settings ⚡️").font(.title)) } } } diff --git a/Music Tools/Views/Tag/TagList.swift b/Music Tools/Views/Tag/TagList.swift new file mode 100644 index 0000000..969e719 --- /dev/null +++ b/Music Tools/Views/Tag/TagList.swift @@ -0,0 +1,62 @@ +// +// TagList.swift +// Music Tools +// +// Created by Andy Pack on 25/04/2020. +// Copyright © 2020 Sarsoo. All rights reserved. +// + +import SwiftUI + +struct TagList: View { + + @EnvironmentObject var liveUser: LiveUser + @State private var showAdd = false // State for showing add modal view + + var body: some View { + NavigationView { + List{ + 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 { + Text("No Tags") + } + } + .pullToRefresh(isShowing: self.$liveUser.isRefreshingTags) { + self.liveUser.refreshTags() + } + .navigationBarTitle(Text("Tags 🎷")) + .navigationBarItems( + leading: + EditButton(), + + trailing: + Button( + action: { self.showAdd = true }, + label: { Text("Add") } + ).sheet(isPresented: $showAdd) { + AddTagSheet(tags: self.$liveUser.tags, username: self.$liveUser.username) + } + ) + } + } +} + +struct TagList_Previews: PreviewProvider { + static var previews: some View { + TagList() + } +} diff --git a/Music Tools/Views/Tag/TagRow.swift b/Music Tools/Views/Tag/TagRow.swift index b64ca25..27a7406 100644 --- a/Music Tools/Views/Tag/TagRow.swift +++ b/Music Tools/Views/Tag/TagRow.swift @@ -12,24 +12,37 @@ import SwiftyJSON struct TagRow: View { @Binding var tag: Tag + @State private var showingNetworkError = false var body: some View { NavigationLink(destination: TagView(tag: $tag)){ HStack { Text(tag.name) - .contextMenu { - - // run force touch - Button(action: { - let api = TagApi.runTag(tag_id: self.tag.tag_id) - RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in - + if tag.count > 0 { + Spacer() + Text("\(tag.count)") + .foregroundColor(.gray) + } + }.contextMenu { + Button(action: { + let api = TagApi.runTag(tag_id: self.tag.tag_id) + RequestBuilder.buildRequest(apiRequest: api) + .validate() + .responseJSON{ response in + switch response.result { + case .success: + break + case .failure: + self.showingNetworkError = true } - }) { - Text("Refresh") - Image(systemName: "arrow.clockwise.circle") } - } + }) { + Text("Refresh") + Image(systemName: "arrow.clockwise.circle") + } + }.alert(isPresented: $showingNetworkError) { + Alert(title: Text("Network Error"), + message: Text("Could not refresh tag")) } } } diff --git a/Music Tools/Views/Tag/TagView.swift b/Music Tools/Views/Tag/TagView.swift index de6ef6e..a4bdecf 100644 --- a/Music Tools/Views/Tag/TagView.swift +++ b/Music Tools/Views/Tag/TagView.swift @@ -43,7 +43,7 @@ struct TagView: View { .foregroundColor(Color.gray) } HStack { - Text("User Total") + Text("Total") Spacer() Text("\(self.tag.total_user_scrobbles)") .font(.title)