added tags, added delete caching for network response filtering

This commit is contained in:
aj 2020-02-20 18:25:28 +00:00
parent 19526c2511
commit 2132e4d855
15 changed files with 584 additions and 38 deletions

View File

@ -23,6 +23,11 @@
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 */; };
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 */; };
E9EA690D23F9A5430012C3E8 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9EA690C23F9A5430012C3E8 /* SceneDelegate.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>"; };
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>"; };
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; };
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>"; };
@ -122,6 +132,7 @@
E98254BC23F9B7A90056D9D3 /* Playlist.swift */,
E97AF45523FC4E7800635494 /* User.swift */,
E97AF46323FD4EEF00635494 /* LiveUser.swift */,
E9E30C2723FEA6BD00574EEF /* Tag.swift */,
);
path = Model;
sourceTree = "<group>";
@ -130,8 +141,9 @@
isa = PBXGroup;
children = (
E98254DA23FB64740056D9D3 /* Network.swift */,
E97AF45A23FC748D00635494 /* UserApi.swift */,
E97AF45F23FC85D600635494 /* PlaylistApi.swift */,
E97AF45A23FC748D00635494 /* UserApi.swift */,
E9E30C2523FEA4EF00574EEF /* TagApi.swift */,
);
path = Network;
sourceTree = "<group>";
@ -139,12 +151,10 @@
E98254C023F9FFDD0056D9D3 /* Views */ = {
isa = PBXGroup;
children = (
E9E30C2F23FEACF700574EEF /* Tag */,
E9E30C2E23FEACDE00574EEF /* Playlist */,
E9EA690E23F9A5430012C3E8 /* RootView.swift */,
E98254C123F9FFF90056D9D3 /* PlaylistView.swift */,
E98254C923FA26600056D9D3 /* PlaylistRow.swift */,
E98254CF23FB00B60056D9D3 /* LoginScreen.swift */,
E97AF46623FD650800635494 /* AddPlaylistSheet.swift */,
E97AF46823FD9E1B00635494 /* PlaylistInputList.swift */,
);
path = Views;
sourceTree = "<group>";
@ -160,6 +170,27 @@
path = Application;
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 = {
isa = PBXGroup;
children = (
@ -363,10 +394,14 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E9E30C2D23FEAB0200574EEF /* TagView.swift in Sources */,
E9E30C2823FEA6BD00574EEF /* Tag.swift in Sources */,
E9E30C3123FEAF2B00574EEF /* TagObjList.swift in Sources */,
E98254CA23FA26600056D9D3 /* PlaylistRow.swift in Sources */,
E9EA690B23F9A5430012C3E8 /* AppDelegate.swift in Sources */,
E9EA690D23F9A5430012C3E8 /* SceneDelegate.swift in Sources */,
E98254DB23FB64740056D9D3 /* Network.swift in Sources */,
E9E30C2A23FEAA3A00574EEF /* TagRow.swift in Sources */,
E97AF46C23FDA90900635494 /* LoginController.swift in Sources */,
E97AF46023FC85D600635494 /* PlaylistApi.swift in Sources */,
E9EA690F23F9A5430012C3E8 /* RootView.swift in Sources */,
@ -376,6 +411,7 @@
E97AF46423FD4EEF00635494 /* LiveUser.swift in Sources */,
E97AF45623FC4E7800635494 /* User.swift in Sources */,
E98254D023FB00B60056D9D3 /* LoginScreen.swift in Sources */,
E9E30C2623FEA4F000574EEF /* TagApi.swift in Sources */,
E97AF46923FD9E1B00635494 /* PlaylistInputList.swift in Sources */,
E97AF45B23FC748D00635494 /* UserApi.swift in Sources */,
);

View File

@ -24,7 +24,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
keychain["username"] = ""
keychain["password"] = ""
liveUser = LiveUser(playlists: [])
liveUser = LiveUser(playlists: [], tags: [])
return true
}

View File

@ -15,9 +15,9 @@
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="center" fixedFrame="YES" image="MusicToolsLogo" translatesAutoresizingMaskIntoConstraints="NO" id="NIx-qI-fR9">
<rect key="frame" x="-59" y="184" width="512" height="512"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="center" image="MusicToolsLogo" id="NIx-qI-fR9">
<rect key="frame" x="-49" y="192" width="512" height="512"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>

View File

@ -29,7 +29,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
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
window.makeKeyAndVisible()
}

View File

@ -9,9 +9,11 @@
import Foundation
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.tags = tags
}
}

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

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

View File

@ -20,6 +20,8 @@ struct PlaylistRow: View {
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
@ -29,6 +31,8 @@ struct PlaylistRow: View {
Text("Refresh")
Image(systemName: "arrow.clockwise.circle")
}
// open force touch
Button(action: {
if let url = URL(string: self.playlist.link) {
UIApplication.shared.open(url)

View File

@ -13,6 +13,8 @@ struct PlaylistView: View {
init(playlist: Playlist) {
self.playlist = playlist
// hide empty items below list
UITableView.appearance().tableFooterView = UIView()
}

View File

@ -14,25 +14,22 @@ struct RootView: View {
@EnvironmentObject var liveUser: LiveUser
@State private var selection = 0
@State private var playlists: Array<Playlist> = []
@State private var selection = 0 // Tab view selection
@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 private var showAdd = false // State for showing add modal view
@State private var onClose = onSheetClose
@State private var justDeleted: Array<Playlist> = []
func onSheetClose() {
self.fetch()
return
}
@State private var justDeletedPlaylists: Array<Playlist> = [] // Cache of recently deleted playlists for removing from next net request
@State private var justDeletedTags: Array<Tag> = []
// refresh playlist list on interval
let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
var body: some View {
TabView {
// PLAYLISTS
NavigationView {
List{
ForEach(playlists) { playlist in
@ -41,7 +38,8 @@ struct RootView: View {
.onDelete { indexSet 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)
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
@ -53,6 +51,8 @@ struct RootView: View {
}
}
.navigationBarTitle(Text("Playlists").font(.title))
// add playlist button
.navigationBarItems(trailing:
Button(
action: { self.showAdd = true },
@ -69,15 +69,39 @@ struct RootView: View {
}
}
.tag(0)
//
//
// TAGS
NavigationView {
List(/*@START_MENU_TOKEN@*/0 ..< 5/*@END_MENU_TOKEN@*/) { item in
Text("Tag")
.font(.title)
List{
ForEach(tags) { tag in
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
}
}
self.tags.remove(atOffsets: indexSet)
}
}
.navigationBarTitle(Text("Tags"))
.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 {
VStack {
@ -86,8 +110,8 @@ struct RootView: View {
}
}
.tag(1)
//
//
// SETTINGS
Text("Settings")
.font(.title)
.tabItem {
@ -117,25 +141,69 @@ struct RootView: View {
fatalError("error parsing reponse")
}
let playlists = json["playlists"].arrayValue.map({ dict in
Playlist.fromDict(dictionary: dict)
}).sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
let playlists = json["playlists"].arrayValue
// parse playlists
.map({ dict in
Playlist.fromDict(dictionary: dict)
})
// sort
.sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
// filter playlists for those recently deleted
.filter { (rxPlaylist) -> Bool in
var deleted = false
for playlist in self.justDeleted {
for playlist in self.justDeletedPlaylists {
if playlist == rxPlaylist {
deleted = true
}
}
return !deleted
}
self.justDeleted = []
// clear cache of recently deleted playlists
self.justDeletedPlaylists = []
// update state
self.liveUser.playlists = playlists
self.playlists = self.liveUser.playlists
}
//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
}
}
}

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

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

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