restructured main views, added totals to rows

This commit is contained in:
aj 2020-04-26 00:11:07 +01:00
parent 6d3929a173
commit fced8c9fc3
12 changed files with 273 additions and 207 deletions

View File

@ -26,6 +26,8 @@
E98254D023FB00B60056D9D3 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98254CF23FB00B60056D9D3 /* LoginScreen.swift */; }; E98254D023FB00B60056D9D3 /* LoginScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98254CF23FB00B60056D9D3 /* LoginScreen.swift */; };
E98254D923FB53780056D9D3 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = E98254D823FB53780056D9D3 /* Alamofire */; }; E98254D923FB53780056D9D3 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = E98254D823FB53780056D9D3 /* Alamofire */; };
E98254DB23FB64740056D9D3 /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98254DA23FB64740056D9D3 /* Network.swift */; }; 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 */; }; E9E30C2623FEA4F000574EEF /* TagApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E30C2523FEA4EF00574EEF /* TagApi.swift */; };
E9E30C2823FEA6BD00574EEF /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E30C2723FEA6BD00574EEF /* Tag.swift */; }; E9E30C2823FEA6BD00574EEF /* Tag.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E30C2723FEA6BD00574EEF /* Tag.swift */; };
E9E30C2A23FEAA3A00574EEF /* TagRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E30C2923FEAA3A00574EEF /* TagRow.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 = "<group>"; }; E98254C923FA26600056D9D3 /* PlaylistRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistRow.swift; sourceTree = "<group>"; };
E98254CF23FB00B60056D9D3 /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreen.swift; sourceTree = "<group>"; }; E98254CF23FB00B60056D9D3 /* LoginScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreen.swift; sourceTree = "<group>"; };
E98254DA23FB64740056D9D3 /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = "<group>"; }; E98254DA23FB64740056D9D3 /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = "<group>"; };
E9CCD5BA2454C57300B5CD6C /* PlaylistList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistList.swift; sourceTree = "<group>"; };
E9CCD5BC2454C64300B5CD6C /* TagList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagList.swift; sourceTree = "<group>"; };
E9E30C2523FEA4EF00574EEF /* TagApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagApi.swift; sourceTree = "<group>"; }; E9E30C2523FEA4EF00574EEF /* TagApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagApi.swift; sourceTree = "<group>"; };
E9E30C2723FEA6BD00574EEF /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; }; E9E30C2723FEA6BD00574EEF /* Tag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tag.swift; sourceTree = "<group>"; };
E9E30C2923FEAA3A00574EEF /* TagRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagRow.swift; sourceTree = "<group>"; }; E9E30C2923FEAA3A00574EEF /* TagRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagRow.swift; sourceTree = "<group>"; };
@ -178,6 +182,7 @@
E98254C923FA26600056D9D3 /* PlaylistRow.swift */, E98254C923FA26600056D9D3 /* PlaylistRow.swift */,
E97AF46623FD650800635494 /* AddPlaylistSheet.swift */, E97AF46623FD650800635494 /* AddPlaylistSheet.swift */,
E97AF46823FD9E1B00635494 /* PlaylistInputList.swift */, E97AF46823FD9E1B00635494 /* PlaylistInputList.swift */,
E9CCD5BA2454C57300B5CD6C /* PlaylistList.swift */,
); );
path = Playlist; path = Playlist;
sourceTree = "<group>"; sourceTree = "<group>";
@ -189,6 +194,7 @@
E9E30C2C23FEAB0200574EEF /* TagView.swift */, E9E30C2C23FEAB0200574EEF /* TagView.swift */,
E9E30C3023FEAF2B00574EEF /* TagObjList.swift */, E9E30C3023FEAF2B00574EEF /* TagObjList.swift */,
E934AC98240DD0E4009869F4 /* AddTagSheet.swift */, E934AC98240DD0E4009869F4 /* AddTagSheet.swift */,
E9CCD5BC2454C64300B5CD6C /* TagList.swift */,
); );
path = Tag; path = Tag;
sourceTree = "<group>"; sourceTree = "<group>";
@ -417,7 +423,9 @@
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 */,
E9CCD5BD2454C64300B5CD6C /* TagList.swift in Sources */,
E9E30C2A23FEAA3A00574EEF /* TagRow.swift in Sources */, E9E30C2A23FEAA3A00574EEF /* TagRow.swift in Sources */,
E9CCD5BB2454C57300B5CD6C /* PlaylistList.swift in Sources */,
E97AF46023FC85D600635494 /* PlaylistApi.swift in Sources */, E97AF46023FC85D600635494 /* PlaylistApi.swift in Sources */,
E9EA690F23F9A5430012C3E8 /* AppSkeleton.swift in Sources */, E9EA690F23F9A5430012C3E8 /* AppSkeleton.swift in Sources */,
E98254BD23F9B7A90056D9D3 /* Playlist.swift in Sources */, E98254BD23F9B7A90056D9D3 /* Playlist.swift in Sources */,

View File

@ -15,15 +15,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch. // 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 return true
} }

View File

@ -27,13 +27,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// Use a UIHostingController as window root view controller. // Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene { if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene) let window = UIWindow(windowScene: windowScene)
let liveUser = LiveUser(playlists: [], tags: [], username: keychain["username"] ?? "", loggedIn: false).loadUserDefaults() 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 = UIHostingController(rootView: contentView.environmentObject(liveUser))
// window.rootViewController = LoginController()
self.window = window self.window = window
window.makeKeyAndVisible() 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 // 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. // to restore the scene back to its current state.
} }
} }

View File

@ -8,6 +8,7 @@
import SwiftUI import SwiftUI
// Root level view loaded by delegate to show either main app or login screen
struct Router: View { struct Router: View {
@EnvironmentObject var liveUser: LiveUser @EnvironmentObject var liveUser: LiveUser

View File

@ -16,120 +16,37 @@ struct AppSkeleton: View {
@EnvironmentObject var liveUser: LiveUser @EnvironmentObject var liveUser: LiveUser
@State private var selection = 0 // Tab view selection @State private var selection = 0 // Tab view selection
@State private var showAdd = false // State for showing add modal view
var body: some View { var body: some View {
TabView { TabView {
// PLAYLISTS PlaylistList()
NavigationView { .tabItem {
List{ VStack {
if(liveUser.playlists.count > 0){ Image(systemName: "music.note.list")
ForEach(liveUser.playlists.indices, id:\.self) { idx in Text("Playlists")
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()
}
} }
} }
.pullToRefresh(isShowing: self.$liveUser.isRefreshingPlaylists) { .tag(0)
self.liveUser.refreshPlaylists()
}
.navigationBarTitle(Text("Playlists").font(.title))
// add playlist button TagList()
.navigationBarItems(trailing: .tabItem {
Button( VStack {
action: { self.showAdd = true }, Image(systemName: "tag")
label: { Text("Add") } Text("Tags")
).sheet(isPresented: $showAdd) {
AddPlaylistSheet(playlists: self.$liveUser.playlists, username: self.$liveUser.username)
}
)
}
.tabItem {
VStack {
Image(systemName: "music.note.list")
Text("Playlists")
}
}
.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()
}
} }
} }
.pullToRefresh(isShowing: self.$liveUser.isRefreshingTags) { .tag(1)
self.liveUser.refreshTags()
}
.navigationBarTitle(Text("Tags").font(.title))
// add playlist button SettingsList()
.navigationBarItems(trailing: .tabItem {
Button( VStack {
action: { self.showAdd = true }, Image(systemName: "slider.horizontal.3")
label: { Text("Add") } Text("Settings")
).sheet(isPresented: $showAdd) {
AddTagSheet(tags: self.$liveUser.tags, username: self.$liveUser.username)
} }
)
}
.tabItem {
VStack {
Image(systemName: "tag")
Text("Tags")
} }
} .tag(2)
.tag(1)
// SETTINGS
NavigationView {
SettingsList()
.navigationBarTitle(Text("Settings").font(.title))
}.tabItem {
VStack {
Image(systemName: "slider.horizontal.3")
Text("Settings")
}
}
.tag(2)
}.onAppear { }.onAppear {
self.fetchAll() self.fetchAll()
} }

View File

@ -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()
}
}

View File

@ -10,35 +10,47 @@ import SwiftUI
import SwiftyJSON import SwiftyJSON
struct PlaylistRow: View { struct PlaylistRow: View {
@Binding var playlist: Playlist @Binding var playlist: Playlist
@State private var showingNetworkError = false
var body: some View { var body: some View {
NavigationLink(destination: PlaylistView(playlist: $playlist)){ NavigationLink(destination: PlaylistView(playlist: $playlist)){
HStack { HStack {
Text(playlist.name) Text(playlist.name)
.contextMenu { if playlist.lastfm_stat_count > 0 {
Spacer()
// run force touch Text("\(playlist.lastfm_stat_count)")
Button(action: { .foregroundColor(.gray)
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")
}
} }
}.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"))
} }
} }
} }

View File

@ -24,6 +24,7 @@ struct PlaylistView: View {
@State private var showingSheet = false @State private var showingSheet = false
@State private var isRefreshing = false @State private var isRefreshing = false
@State private var showingNetworkError = false
var chartStyle: ChartStyle { var chartStyle: ChartStyle {
get { get {
@ -211,6 +212,10 @@ struct PlaylistView: View {
self.$chart_limit.wrappedValue = playlist.chart_limit 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) { func changeChartRange(newRange: LastFmRange) {
@ -222,8 +227,15 @@ struct PlaylistView: View {
func runPlaylist() { func runPlaylist() {
let api = PlaylistApi.runPlaylist(name: playlist.name) 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 //TODO: do better error checking
} }

View File

@ -13,65 +13,63 @@ struct SettingsList: View {
@EnvironmentObject var liveUser: LiveUser @EnvironmentObject var liveUser: LiveUser
init(){
UITableView.appearance().tableFooterView = UIView()
}
var body: some View { var body: some View {
VStack{ NavigationView {
List{ List{
Section { Section {
Button(action: { Button(action: {
if let url = URL(string: "https://music.sarsoo.xyz") { if let url = URL(string: "https://music.sarsoo.xyz") {
UIApplication.shared.open(url) 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: { Section(
let keychain = Keychain(service: "xyz.sarsoo.music.login") header:
do { Text("Development"),
try keychain.remove("username") footer:
try keychain.remove("password") HStack{
Spacer()
self.liveUser.loggedIn = false Image("APFooter")
.resizable()
} catch let error { .aspectRatio(contentMode: .fit)
debugPrint("Could not clear keychain, \(error)") .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( .listStyle(GroupedListStyle())
header: .navigationBarTitle(Text("Settings ⚡️").font(.title))
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())
} }
} }
} }

View File

@ -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()
}
}

View File

@ -12,24 +12,37 @@ import SwiftyJSON
struct TagRow: View { struct TagRow: View {
@Binding var tag: Tag @Binding var tag: Tag
@State private var showingNetworkError = false
var body: some View { var body: some View {
NavigationLink(destination: TagView(tag: $tag)){ NavigationLink(destination: TagView(tag: $tag)){
HStack { HStack {
Text(tag.name) Text(tag.name)
.contextMenu { if tag.count > 0 {
Spacer()
// run force touch Text("\(tag.count)")
Button(action: { .foregroundColor(.gray)
let api = TagApi.runTag(tag_id: self.tag.tag_id) }
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in }.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"))
} }
} }
} }

View File

@ -43,7 +43,7 @@ struct TagView: View {
.foregroundColor(Color.gray) .foregroundColor(Color.gray)
} }
HStack { HStack {
Text("User Total") Text("Total")
Spacer() Spacer()
Text("\(self.tag.total_user_scrobbles)") Text("\(self.tag.total_user_scrobbles)")
.font(.title) .font(.title)