added tags, added delete caching for network response filtering
This commit is contained in:
parent
19526c2511
commit
2132e4d855
@ -23,6 +23,11 @@
|
|||||||
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 */; };
|
||||||
|
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 */; };
|
||||||
|
E9E30C2D23FEAB0200574EEF /* TagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E30C2C23FEAB0200574EEF /* TagView.swift */; };
|
||||||
|
E9E30C3123FEAF2B00574EEF /* TagObjList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E30C3023FEAF2B00574EEF /* TagObjList.swift */; };
|
||||||
E9EA690B23F9A5430012C3E8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EA690A23F9A5430012C3E8 /* AppDelegate.swift */; };
|
E9EA690B23F9A5430012C3E8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EA690A23F9A5430012C3E8 /* AppDelegate.swift */; };
|
||||||
E9EA690D23F9A5430012C3E8 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EA690C23F9A5430012C3E8 /* SceneDelegate.swift */; };
|
E9EA690D23F9A5430012C3E8 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EA690C23F9A5430012C3E8 /* SceneDelegate.swift */; };
|
||||||
E9EA690F23F9A5430012C3E8 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EA690E23F9A5430012C3E8 /* RootView.swift */; };
|
E9EA690F23F9A5430012C3E8 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EA690E23F9A5430012C3E8 /* RootView.swift */; };
|
||||||
@ -64,6 +69,11 @@
|
|||||||
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>"; };
|
||||||
|
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>"; };
|
||||||
|
E9E30C2C23FEAB0200574EEF /* TagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagView.swift; sourceTree = "<group>"; };
|
||||||
|
E9E30C3023FEAF2B00574EEF /* TagObjList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagObjList.swift; sourceTree = "<group>"; };
|
||||||
E9EA690723F9A5430012C3E8 /* Music Tools.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Music Tools.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
E9EA690723F9A5430012C3E8 /* Music Tools.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Music Tools.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
E9EA690A23F9A5430012C3E8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
E9EA690A23F9A5430012C3E8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
E9EA690C23F9A5430012C3E8 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
E9EA690C23F9A5430012C3E8 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||||
@ -122,6 +132,7 @@
|
|||||||
E98254BC23F9B7A90056D9D3 /* Playlist.swift */,
|
E98254BC23F9B7A90056D9D3 /* Playlist.swift */,
|
||||||
E97AF45523FC4E7800635494 /* User.swift */,
|
E97AF45523FC4E7800635494 /* User.swift */,
|
||||||
E97AF46323FD4EEF00635494 /* LiveUser.swift */,
|
E97AF46323FD4EEF00635494 /* LiveUser.swift */,
|
||||||
|
E9E30C2723FEA6BD00574EEF /* Tag.swift */,
|
||||||
);
|
);
|
||||||
path = Model;
|
path = Model;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -130,8 +141,9 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
E98254DA23FB64740056D9D3 /* Network.swift */,
|
E98254DA23FB64740056D9D3 /* Network.swift */,
|
||||||
E97AF45A23FC748D00635494 /* UserApi.swift */,
|
|
||||||
E97AF45F23FC85D600635494 /* PlaylistApi.swift */,
|
E97AF45F23FC85D600635494 /* PlaylistApi.swift */,
|
||||||
|
E97AF45A23FC748D00635494 /* UserApi.swift */,
|
||||||
|
E9E30C2523FEA4EF00574EEF /* TagApi.swift */,
|
||||||
);
|
);
|
||||||
path = Network;
|
path = Network;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -139,12 +151,10 @@
|
|||||||
E98254C023F9FFDD0056D9D3 /* Views */ = {
|
E98254C023F9FFDD0056D9D3 /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
E9E30C2F23FEACF700574EEF /* Tag */,
|
||||||
|
E9E30C2E23FEACDE00574EEF /* Playlist */,
|
||||||
E9EA690E23F9A5430012C3E8 /* RootView.swift */,
|
E9EA690E23F9A5430012C3E8 /* RootView.swift */,
|
||||||
E98254C123F9FFF90056D9D3 /* PlaylistView.swift */,
|
|
||||||
E98254C923FA26600056D9D3 /* PlaylistRow.swift */,
|
|
||||||
E98254CF23FB00B60056D9D3 /* LoginScreen.swift */,
|
E98254CF23FB00B60056D9D3 /* LoginScreen.swift */,
|
||||||
E97AF46623FD650800635494 /* AddPlaylistSheet.swift */,
|
|
||||||
E97AF46823FD9E1B00635494 /* PlaylistInputList.swift */,
|
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -160,6 +170,27 @@
|
|||||||
path = Application;
|
path = Application;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
E9E30C2E23FEACDE00574EEF /* Playlist */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E98254C123F9FFF90056D9D3 /* PlaylistView.swift */,
|
||||||
|
E98254C923FA26600056D9D3 /* PlaylistRow.swift */,
|
||||||
|
E97AF46623FD650800635494 /* AddPlaylistSheet.swift */,
|
||||||
|
E97AF46823FD9E1B00635494 /* PlaylistInputList.swift */,
|
||||||
|
);
|
||||||
|
path = Playlist;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
E9E30C2F23FEACF700574EEF /* Tag */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
E9E30C2923FEAA3A00574EEF /* TagRow.swift */,
|
||||||
|
E9E30C2C23FEAB0200574EEF /* TagView.swift */,
|
||||||
|
E9E30C3023FEAF2B00574EEF /* TagObjList.swift */,
|
||||||
|
);
|
||||||
|
path = Tag;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
E9EA68FE23F9A5430012C3E8 = {
|
E9EA68FE23F9A5430012C3E8 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -363,10 +394,14 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
E9E30C2D23FEAB0200574EEF /* TagView.swift in Sources */,
|
||||||
|
E9E30C2823FEA6BD00574EEF /* Tag.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 */,
|
||||||
E9EA690D23F9A5430012C3E8 /* SceneDelegate.swift in Sources */,
|
E9EA690D23F9A5430012C3E8 /* SceneDelegate.swift in Sources */,
|
||||||
E98254DB23FB64740056D9D3 /* Network.swift in Sources */,
|
E98254DB23FB64740056D9D3 /* Network.swift in Sources */,
|
||||||
|
E9E30C2A23FEAA3A00574EEF /* TagRow.swift in Sources */,
|
||||||
E97AF46C23FDA90900635494 /* LoginController.swift in Sources */,
|
E97AF46C23FDA90900635494 /* LoginController.swift in Sources */,
|
||||||
E97AF46023FC85D600635494 /* PlaylistApi.swift in Sources */,
|
E97AF46023FC85D600635494 /* PlaylistApi.swift in Sources */,
|
||||||
E9EA690F23F9A5430012C3E8 /* RootView.swift in Sources */,
|
E9EA690F23F9A5430012C3E8 /* RootView.swift in Sources */,
|
||||||
@ -376,6 +411,7 @@
|
|||||||
E97AF46423FD4EEF00635494 /* LiveUser.swift in Sources */,
|
E97AF46423FD4EEF00635494 /* LiveUser.swift in Sources */,
|
||||||
E97AF45623FC4E7800635494 /* User.swift in Sources */,
|
E97AF45623FC4E7800635494 /* User.swift in Sources */,
|
||||||
E98254D023FB00B60056D9D3 /* LoginScreen.swift in Sources */,
|
E98254D023FB00B60056D9D3 /* LoginScreen.swift in Sources */,
|
||||||
|
E9E30C2623FEA4F000574EEF /* TagApi.swift in Sources */,
|
||||||
E97AF46923FD9E1B00635494 /* PlaylistInputList.swift in Sources */,
|
E97AF46923FD9E1B00635494 /* PlaylistInputList.swift in Sources */,
|
||||||
E97AF45B23FC748D00635494 /* UserApi.swift in Sources */,
|
E97AF45B23FC748D00635494 /* UserApi.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
@ -24,7 +24,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
keychain["username"] = ""
|
keychain["username"] = ""
|
||||||
keychain["password"] = ""
|
keychain["password"] = ""
|
||||||
|
|
||||||
liveUser = LiveUser(playlists: [])
|
liveUser = LiveUser(playlists: [], tags: [])
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,9 @@
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="center" fixedFrame="YES" image="MusicToolsLogo" translatesAutoresizingMaskIntoConstraints="NO" id="NIx-qI-fR9">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="center" image="MusicToolsLogo" id="NIx-qI-fR9">
|
||||||
<rect key="frame" x="-59" y="184" width="512" height="512"/>
|
<rect key="frame" x="-49" y="192" width="512" height="512"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||||
|
@ -29,7 +29,7 @@ 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)
|
||||||
window.rootViewController = UIHostingController(rootView: contentView.environmentObject(liveUser ?? LiveUser(playlists: [])))
|
window.rootViewController = UIHostingController(rootView: contentView.environmentObject(liveUser ?? LiveUser(playlists: [], tags: [])))
|
||||||
self.window = window
|
self.window = window
|
||||||
window.makeKeyAndVisible()
|
window.makeKeyAndVisible()
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,11 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class LiveUser: ObservableObject {
|
class LiveUser: ObservableObject {
|
||||||
var playlists: Array<Playlist>
|
var playlists: [Playlist]
|
||||||
|
var tags: [Tag]
|
||||||
|
|
||||||
init(playlists: Array<Playlist>) {
|
init(playlists: [Playlist], tags: [Tag]) {
|
||||||
self.playlists = playlists
|
self.playlists = playlists
|
||||||
|
self.tags = tags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
82
Music Tools/Model/Tag.swift
Normal file
82
Music Tools/Model/Tag.swift
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
//
|
||||||
|
// Tag.swift
|
||||||
|
// Music Tools
|
||||||
|
//
|
||||||
|
// Created by Andy Pack on 20/02/2020.
|
||||||
|
// Copyright © 2020 Sarsoo. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SwiftyJSON
|
||||||
|
|
||||||
|
class Tag: Identifiable, Equatable {
|
||||||
|
|
||||||
|
//MARK: Properties
|
||||||
|
|
||||||
|
var tag_id: String
|
||||||
|
var name: String
|
||||||
|
var username: String
|
||||||
|
|
||||||
|
var tracks: [JSON]
|
||||||
|
var albums: [JSON]
|
||||||
|
var artists: [JSON]
|
||||||
|
|
||||||
|
var count: Int
|
||||||
|
var proportion: Double
|
||||||
|
var total_user_scrobbles: Int
|
||||||
|
|
||||||
|
var last_updated: String
|
||||||
|
|
||||||
|
//MARK: Initialization
|
||||||
|
|
||||||
|
init(tag_id: String,
|
||||||
|
name: String,
|
||||||
|
username: String,
|
||||||
|
|
||||||
|
tracks: [JSON],
|
||||||
|
albums: [JSON],
|
||||||
|
artists: [JSON],
|
||||||
|
|
||||||
|
count: Int,
|
||||||
|
proportion: Double,
|
||||||
|
total_user_scrobbles: Int,
|
||||||
|
|
||||||
|
last_updated: String){
|
||||||
|
|
||||||
|
self.tag_id = tag_id
|
||||||
|
self.name = name
|
||||||
|
self.username = username
|
||||||
|
|
||||||
|
self.tracks = tracks
|
||||||
|
self.albums = albums
|
||||||
|
self.artists = artists
|
||||||
|
|
||||||
|
self.count = count
|
||||||
|
self.proportion = proportion
|
||||||
|
self.total_user_scrobbles = total_user_scrobbles
|
||||||
|
|
||||||
|
self.last_updated = last_updated
|
||||||
|
}
|
||||||
|
|
||||||
|
static func fromDict(dictionary: JSON) -> Tag {
|
||||||
|
return Tag(tag_id: dictionary["tag_id"].stringValue,
|
||||||
|
name: dictionary["name"].stringValue,
|
||||||
|
username: dictionary["username"].stringValue,
|
||||||
|
|
||||||
|
tracks: dictionary["tracks"].arrayValue,
|
||||||
|
albums: dictionary["albums"].arrayValue,
|
||||||
|
artists: dictionary["artists"].arrayValue,
|
||||||
|
|
||||||
|
count: dictionary["count"].intValue,
|
||||||
|
proportion: dictionary["proportion"].doubleValue,
|
||||||
|
total_user_scrobbles: dictionary["total_user_scrobbles"].intValue,
|
||||||
|
|
||||||
|
last_updated: dictionary["last_updated"].stringValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func == (lhs: Tag, rhs: Tag) -> Bool {
|
||||||
|
return lhs.tag_id == rhs.tag_id
|
||||||
|
// && lhs.username == rhs.username
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
96
Music Tools/Network/TagApi.swift
Normal file
96
Music Tools/Network/TagApi.swift
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
//
|
||||||
|
// TagApi.swift
|
||||||
|
// Music Tools
|
||||||
|
//
|
||||||
|
// Created by Andy Pack on 20/02/2020.
|
||||||
|
// Copyright © 2020 Sarsoo. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Alamofire
|
||||||
|
import SwiftyJSON
|
||||||
|
|
||||||
|
public enum TagApi {
|
||||||
|
case getTags
|
||||||
|
case runTag(tag_id: String)
|
||||||
|
case updateTag(tag_id: String, updates: JSON)
|
||||||
|
case deleteTag(tag_id: String)
|
||||||
|
case newTag(tag_id: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TagApi: ApiRequest {
|
||||||
|
var domain: String {
|
||||||
|
return ApiRequestDefaults.domain
|
||||||
|
}
|
||||||
|
|
||||||
|
var path: String {
|
||||||
|
switch self {
|
||||||
|
case .getTags:
|
||||||
|
return "api/tag"
|
||||||
|
case .runTag(let tag_id):
|
||||||
|
return "api/tag/\(tag_id)/update"
|
||||||
|
case .updateTag(let tag_id):
|
||||||
|
return "api/tag/\(tag_id)"
|
||||||
|
case .deleteTag(let tag_id):
|
||||||
|
return "api/tag/\(tag_id)"
|
||||||
|
case .newTag(let tag_id):
|
||||||
|
return "api/tag/\(tag_id)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpMethod: Alamofire.HTTPMethod {
|
||||||
|
switch self {
|
||||||
|
case .getTags:
|
||||||
|
return .get
|
||||||
|
case .runTag:
|
||||||
|
return .get
|
||||||
|
case .updateTag:
|
||||||
|
return .put
|
||||||
|
case .deleteTag:
|
||||||
|
return .delete
|
||||||
|
case .newTag:
|
||||||
|
return .post
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var parameters: JSON? {
|
||||||
|
switch self {
|
||||||
|
case .getTags:
|
||||||
|
return nil
|
||||||
|
case .runTag:
|
||||||
|
return nil
|
||||||
|
case .updateTag(let tag_id, let updates):
|
||||||
|
return updates
|
||||||
|
case .deleteTag:
|
||||||
|
return nil
|
||||||
|
case .newTag:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var parameterType: ParameterEncoder? {
|
||||||
|
switch self {
|
||||||
|
case .getTags:
|
||||||
|
return nil
|
||||||
|
case .runTag:
|
||||||
|
return nil
|
||||||
|
case .updateTag:
|
||||||
|
return JSONParameterEncoder.default
|
||||||
|
case .deleteTag:
|
||||||
|
return nil
|
||||||
|
case .newTag:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var headers: HTTPHeaders? {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var authMethod: AuthMethod? {
|
||||||
|
return ApiRequestDefaults.authMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,8 @@ struct PlaylistRow: View {
|
|||||||
HStack {
|
HStack {
|
||||||
Text(playlist.name)
|
Text(playlist.name)
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
|
|
||||||
|
// run force touch
|
||||||
Button(action: {
|
Button(action: {
|
||||||
let api = PlaylistApi.runPlaylist(name: self.playlist.name)
|
let api = PlaylistApi.runPlaylist(name: self.playlist.name)
|
||||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
@ -29,6 +31,8 @@ struct PlaylistRow: View {
|
|||||||
Text("Refresh")
|
Text("Refresh")
|
||||||
Image(systemName: "arrow.clockwise.circle")
|
Image(systemName: "arrow.clockwise.circle")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// open force touch
|
||||||
Button(action: {
|
Button(action: {
|
||||||
if let url = URL(string: self.playlist.link) {
|
if let url = URL(string: self.playlist.link) {
|
||||||
UIApplication.shared.open(url)
|
UIApplication.shared.open(url)
|
@ -13,6 +13,8 @@ struct PlaylistView: View {
|
|||||||
|
|
||||||
init(playlist: Playlist) {
|
init(playlist: Playlist) {
|
||||||
self.playlist = playlist
|
self.playlist = playlist
|
||||||
|
|
||||||
|
// hide empty items below list
|
||||||
UITableView.appearance().tableFooterView = UIView()
|
UITableView.appearance().tableFooterView = UIView()
|
||||||
}
|
}
|
||||||
|
|
@ -14,25 +14,22 @@ struct RootView: View {
|
|||||||
|
|
||||||
@EnvironmentObject var liveUser: LiveUser
|
@EnvironmentObject var liveUser: LiveUser
|
||||||
|
|
||||||
@State private var selection = 0
|
@State private var selection = 0 // Tab view selection
|
||||||
@State private var playlists: Array<Playlist> = []
|
@State private var playlists: Array<Playlist> = [] // Network pulled playlists
|
||||||
|
@State private var tags: Array<Tag> = [] // Network pulled tags
|
||||||
|
|
||||||
@State private var isLoading = true
|
@State private var showAdd = false // State for showing add modal view
|
||||||
@State private var showAdd = false
|
|
||||||
|
|
||||||
@State private var onClose = onSheetClose
|
@State private var justDeletedPlaylists: Array<Playlist> = [] // Cache of recently deleted playlists for removing from next net request
|
||||||
|
@State private var justDeletedTags: Array<Tag> = []
|
||||||
@State private var justDeleted: Array<Playlist> = []
|
|
||||||
|
|
||||||
func onSheetClose() {
|
|
||||||
self.fetch()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// refresh playlist list on interval
|
||||||
let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
|
let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TabView {
|
TabView {
|
||||||
|
|
||||||
|
// PLAYLISTS
|
||||||
NavigationView {
|
NavigationView {
|
||||||
List{
|
List{
|
||||||
ForEach(playlists) { playlist in
|
ForEach(playlists) { playlist in
|
||||||
@ -41,7 +38,8 @@ struct RootView: View {
|
|||||||
.onDelete { indexSet in
|
.onDelete { indexSet in
|
||||||
|
|
||||||
indexSet.forEach { index in
|
indexSet.forEach { index in
|
||||||
self.justDeleted.append(self.playlists[index])
|
// add to recently deleted playlist cache
|
||||||
|
self.justDeletedPlaylists.append(self.playlists[index])
|
||||||
|
|
||||||
let api = PlaylistApi.deletePlaylist(name: self.playlists[index].name)
|
let api = PlaylistApi.deletePlaylist(name: self.playlists[index].name)
|
||||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
@ -53,6 +51,8 @@ struct RootView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationBarTitle(Text("Playlists").font(.title))
|
.navigationBarTitle(Text("Playlists").font(.title))
|
||||||
|
|
||||||
|
// add playlist button
|
||||||
.navigationBarItems(trailing:
|
.navigationBarItems(trailing:
|
||||||
Button(
|
Button(
|
||||||
action: { self.showAdd = true },
|
action: { self.showAdd = true },
|
||||||
@ -69,15 +69,39 @@ struct RootView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.tag(0)
|
.tag(0)
|
||||||
//
|
|
||||||
//
|
// TAGS
|
||||||
NavigationView {
|
NavigationView {
|
||||||
List(/*@START_MENU_TOKEN@*/0 ..< 5/*@END_MENU_TOKEN@*/) { item in
|
List{
|
||||||
Text("Tag")
|
ForEach(tags) { tag in
|
||||||
.font(.title)
|
TagRow(tag: tag)
|
||||||
|
}
|
||||||
|
.onDelete { indexSet in
|
||||||
|
|
||||||
|
indexSet.forEach { index in
|
||||||
|
// add to recently deleted playlist cache
|
||||||
|
self.justDeletedTags.append(self.tags[index])
|
||||||
|
|
||||||
|
let api = TagApi.deleteTag(tag_id: self.tags[index].tag_id)
|
||||||
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
|
|
||||||
}
|
}
|
||||||
.navigationBarTitle(Text("Tags"))
|
}
|
||||||
|
|
||||||
|
self.tags.remove(atOffsets: indexSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationBarTitle(Text("Tags").font(.title))
|
||||||
|
|
||||||
|
// add playlist button
|
||||||
|
.navigationBarItems(trailing:
|
||||||
|
Button(
|
||||||
|
action: { self.showAdd = true },
|
||||||
|
label: { Text("Add") }
|
||||||
|
).sheet(isPresented: $showAdd) {
|
||||||
|
AddPlaylistSheet(state: self.$showAdd, playlists: self.$playlists)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.tabItem {
|
.tabItem {
|
||||||
VStack {
|
VStack {
|
||||||
@ -86,8 +110,8 @@ struct RootView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.tag(1)
|
.tag(1)
|
||||||
//
|
|
||||||
//
|
// SETTINGS
|
||||||
Text("Settings")
|
Text("Settings")
|
||||||
.font(.title)
|
.font(.title)
|
||||||
.tabItem {
|
.tabItem {
|
||||||
@ -117,25 +141,69 @@ struct RootView: View {
|
|||||||
fatalError("error parsing reponse")
|
fatalError("error parsing reponse")
|
||||||
}
|
}
|
||||||
|
|
||||||
let playlists = json["playlists"].arrayValue.map({ dict in
|
let playlists = json["playlists"].arrayValue
|
||||||
|
// parse playlists
|
||||||
|
.map({ dict in
|
||||||
Playlist.fromDict(dictionary: dict)
|
Playlist.fromDict(dictionary: dict)
|
||||||
}).sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
|
})
|
||||||
|
// sort
|
||||||
|
.sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
|
||||||
|
// filter playlists for those recently deleted
|
||||||
.filter { (rxPlaylist) -> Bool in
|
.filter { (rxPlaylist) -> Bool in
|
||||||
|
|
||||||
var deleted = false
|
var deleted = false
|
||||||
for playlist in self.justDeleted {
|
for playlist in self.justDeletedPlaylists {
|
||||||
if playlist == rxPlaylist {
|
if playlist == rxPlaylist {
|
||||||
deleted = true
|
deleted = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return !deleted
|
return !deleted
|
||||||
}
|
}
|
||||||
self.justDeleted = []
|
// clear cache of recently deleted playlists
|
||||||
|
self.justDeletedPlaylists = []
|
||||||
|
|
||||||
|
// update state
|
||||||
self.liveUser.playlists = playlists
|
self.liveUser.playlists = playlists
|
||||||
self.playlists = self.liveUser.playlists
|
self.playlists = self.liveUser.playlists
|
||||||
}
|
}
|
||||||
//TODO: do better error checking
|
//TODO: do better error checking
|
||||||
|
|
||||||
|
let tagApi = TagApi.getTags
|
||||||
|
RequestBuilder.buildRequest(apiRequest: tagApi).responseJSON{ response in
|
||||||
|
|
||||||
|
guard let data = response.data else {
|
||||||
|
fatalError("error getting playlists")
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let json = try? JSON(data: data) else {
|
||||||
|
fatalError("error parsing reponse")
|
||||||
|
}
|
||||||
|
|
||||||
|
let tags = json["tags"].arrayValue
|
||||||
|
// parse playlists
|
||||||
|
.map({ dict in
|
||||||
|
Tag.fromDict(dictionary: dict)
|
||||||
|
})
|
||||||
|
// sort
|
||||||
|
.sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
|
||||||
|
// filter playlists for those recently deleted
|
||||||
|
.filter { (rxTag) -> Bool in
|
||||||
|
|
||||||
|
var deleted = false
|
||||||
|
for tag in self.justDeletedTags {
|
||||||
|
if tag == rxTag {
|
||||||
|
deleted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !deleted
|
||||||
|
}
|
||||||
|
// clear cache of recently deleted playlists
|
||||||
|
self.justDeletedTags = []
|
||||||
|
|
||||||
|
// update state
|
||||||
|
self.liveUser.tags = tags
|
||||||
|
self.tags = self.liveUser.tags
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
72
Music Tools/Views/Tag/TagObjList.swift
Normal file
72
Music Tools/Views/Tag/TagObjList.swift
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
//
|
||||||
|
// TagObjList.swift
|
||||||
|
// Music Tools
|
||||||
|
//
|
||||||
|
// Created by Andy Pack on 20/02/2020.
|
||||||
|
// Copyright © 2020 Sarsoo. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import SwiftyJSON
|
||||||
|
|
||||||
|
struct MusicObj: Identifiable {
|
||||||
|
var id = UUID()
|
||||||
|
var name: String
|
||||||
|
var artist: String
|
||||||
|
var count: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TagObjList: View {
|
||||||
|
|
||||||
|
var objs: Array<MusicObj> = []
|
||||||
|
var objType: String
|
||||||
|
|
||||||
|
init(objs: Array<JSON>, objType: String){
|
||||||
|
self.objType = objType
|
||||||
|
self.objs = objs.map { (obj) -> MusicObj in
|
||||||
|
return MusicObj(name: obj["name"].stringValue,
|
||||||
|
artist: obj["artist"].stringValue,
|
||||||
|
count: obj["count"].intValue)
|
||||||
|
}.sorted(by: { $0.count > $1.count })
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
init(musicObjs: [MusicObj], objType: String){
|
||||||
|
self.objType = objType
|
||||||
|
self.objs = objs.sorted(by: { $0.count > $1.count })
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
return List(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
Text("\(obj.count)")
|
||||||
|
.foregroundColor(Color.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationBarTitle(Text(objType))
|
||||||
|
.navigationBarItems(trailing:
|
||||||
|
Button(
|
||||||
|
action: { },
|
||||||
|
label: { Image(systemName: "plus.circle") }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TagObjList_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
TagObjList(musicObjs: [
|
||||||
|
MusicObj(name: "To Pimp A Butterfly", artist: "Kendrick Lamar", count: 10)
|
||||||
|
], objType: "Albums")
|
||||||
|
}
|
||||||
|
}
|
58
Music Tools/Views/Tag/TagRow.swift
Normal file
58
Music Tools/Views/Tag/TagRow.swift
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
//
|
||||||
|
// TagRow.swift
|
||||||
|
// Music Tools
|
||||||
|
//
|
||||||
|
// Created by Andy Pack on 20/02/2020.
|
||||||
|
// Copyright © 2020 Sarsoo. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import SwiftyJSON
|
||||||
|
|
||||||
|
struct TagRow: View {
|
||||||
|
|
||||||
|
@EnvironmentObject var liveUser: LiveUser
|
||||||
|
|
||||||
|
var tag: Tag
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text("Refresh")
|
||||||
|
Image(systemName: "arrow.clockwise.circle")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TagRow_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
TagRow(tag:
|
||||||
|
Tag(tag_id: "tag_id",
|
||||||
|
name: "tag name",
|
||||||
|
username: "andy",
|
||||||
|
|
||||||
|
tracks: [],
|
||||||
|
albums: [],
|
||||||
|
artists: [],
|
||||||
|
|
||||||
|
count: 20,
|
||||||
|
proportion: 0.5,
|
||||||
|
total_user_scrobbles: 2000,
|
||||||
|
|
||||||
|
last_updated: "10th Feb")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
126
Music Tools/Views/Tag/TagView.swift
Normal file
126
Music Tools/Views/Tag/TagView.swift
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
//
|
||||||
|
// TagView.swift
|
||||||
|
// Music Tools
|
||||||
|
//
|
||||||
|
// Created by Andy Pack on 20/02/2020.
|
||||||
|
// Copyright © 2020 Sarsoo. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import SwiftyJSON
|
||||||
|
|
||||||
|
struct TagView: View {
|
||||||
|
|
||||||
|
init(tag: Tag) {
|
||||||
|
self.tag = tag
|
||||||
|
|
||||||
|
// hide empty items below list
|
||||||
|
UITableView.appearance().tableFooterView = UIView()
|
||||||
|
}
|
||||||
|
|
||||||
|
var tag: Tag
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
List {
|
||||||
|
Section(header: Text("Stats")){
|
||||||
|
HStack {
|
||||||
|
Text("Count")
|
||||||
|
Spacer()
|
||||||
|
Text("\(self.tag.count)")
|
||||||
|
.font(.title)
|
||||||
|
.foregroundColor(Color.gray)
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
Text("Proportion")
|
||||||
|
Spacer()
|
||||||
|
Text(String(format: "%.2f%%", self.tag.proportion))
|
||||||
|
.font(.title)
|
||||||
|
.foregroundColor(Color.gray)
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
Text("User Total")
|
||||||
|
Spacer()
|
||||||
|
Text("\(self.tag.total_user_scrobbles)")
|
||||||
|
.font(.title)
|
||||||
|
.foregroundColor(Color.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Section(header: Text("Music")){
|
||||||
|
NavigationLink(destination: TagObjList(objs: self.tag.artists,
|
||||||
|
objType: "Artists")) {
|
||||||
|
HStack {
|
||||||
|
Text("Artists")
|
||||||
|
Spacer()
|
||||||
|
Text("\(self.tag.artists.count)")
|
||||||
|
.foregroundColor(Color.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationLink(destination: TagObjList(objs: self.tag.albums,
|
||||||
|
objType: "Albums")) {
|
||||||
|
HStack {
|
||||||
|
Text("Albums")
|
||||||
|
Spacer()
|
||||||
|
Text("\(self.tag.albums.count)")
|
||||||
|
.foregroundColor(Color.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NavigationLink(destination: TagObjList(objs: self.tag.tracks,
|
||||||
|
objType: "Tracks")) {
|
||||||
|
HStack {
|
||||||
|
Text("Tracks")
|
||||||
|
Spacer()
|
||||||
|
Text("\(self.tag.tracks.count)")
|
||||||
|
.foregroundColor(Color.gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Section(header: Text("Actions")){
|
||||||
|
Button(action: { self.runTag() }) {
|
||||||
|
Text("Update")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationBarTitle(Text(tag.name))
|
||||||
|
.onAppear {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTag() {
|
||||||
|
let api = TagApi.runTag(tag_id: tag.tag_id)
|
||||||
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
|
|
||||||
|
}
|
||||||
|
//TODO: do better error checking
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateTag(updates: JSON) {
|
||||||
|
let api = TagApi.updateTag(tag_id: tag.tag_id, updates: updates)
|
||||||
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
|
|
||||||
|
}
|
||||||
|
//TODO: do better error checking
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TagView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
TagView(tag:
|
||||||
|
Tag(tag_id: "tag_id",
|
||||||
|
name: "tag name",
|
||||||
|
username: "andy",
|
||||||
|
|
||||||
|
tracks: [],
|
||||||
|
albums: [],
|
||||||
|
artists: [],
|
||||||
|
|
||||||
|
count: 20,
|
||||||
|
proportion: 0.5,
|
||||||
|
total_user_scrobbles: 2000,
|
||||||
|
|
||||||
|
last_updated: "10th Feb")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user