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 */; };
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 = "<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>"; };
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>"; };
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>"; };
@ -178,6 +182,7 @@
E98254C923FA26600056D9D3 /* PlaylistRow.swift */,
E97AF46623FD650800635494 /* AddPlaylistSheet.swift */,
E97AF46823FD9E1B00635494 /* PlaylistInputList.swift */,
E9CCD5BA2454C57300B5CD6C /* PlaylistList.swift */,
);
path = Playlist;
sourceTree = "<group>";
@ -189,6 +194,7 @@
E9E30C2C23FEAB0200574EEF /* TagView.swift */,
E9E30C3023FEAF2B00574EEF /* TagObjList.swift */,
E934AC98240DD0E4009869F4 /* AddTagSheet.swift */,
E9CCD5BC2454C64300B5CD6C /* TagList.swift */,
);
path = Tag;
sourceTree = "<group>";
@ -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 */,

View File

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

View File

@ -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.
}
}

View File

@ -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

View File

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

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
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"))
}
}
}

View File

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

View File

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

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 {
@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"))
}
}
}

View File

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