fully bound tag/playlist views to same environment object authoritative source, added pull to refresh on object views, added footer logo in settings
This commit is contained in:
parent
6318ab4f16
commit
a906b5396a
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
E92F94822401412100B6B721 /* SwiftUIRefresh in Frameworks */ = {isa = PBXBuildFile; productRef = E92F94812401412100B6B721 /* SwiftUIRefresh */; };
|
E92F94822401412100B6B721 /* SwiftUIRefresh in Frameworks */ = {isa = PBXBuildFile; productRef = E92F94812401412100B6B721 /* SwiftUIRefresh */; };
|
||||||
|
E934AC99240DD0E4009869F4 /* AddTagSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E934AC98240DD0E4009869F4 /* AddTagSheet.swift */; };
|
||||||
E97AF45623FC4E7800635494 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF45523FC4E7800635494 /* User.swift */; };
|
E97AF45623FC4E7800635494 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF45523FC4E7800635494 /* User.swift */; };
|
||||||
E97AF45923FC50EC00635494 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = E97AF45823FC50EC00635494 /* SwiftyJSON */; };
|
E97AF45923FC50EC00635494 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = E97AF45823FC50EC00635494 /* SwiftyJSON */; };
|
||||||
E97AF45B23FC748D00635494 /* UserApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF45A23FC748D00635494 /* UserApi.swift */; };
|
E97AF45B23FC748D00635494 /* UserApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97AF45A23FC748D00635494 /* UserApi.swift */; };
|
||||||
@ -58,6 +59,7 @@
|
|||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
E934AC98240DD0E4009869F4 /* AddTagSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTagSheet.swift; sourceTree = "<group>"; };
|
||||||
E97AF45523FC4E7800635494 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
|
E97AF45523FC4E7800635494 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
|
||||||
E97AF45A23FC748D00635494 /* UserApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserApi.swift; sourceTree = "<group>"; };
|
E97AF45A23FC748D00635494 /* UserApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserApi.swift; sourceTree = "<group>"; };
|
||||||
E97AF45F23FC85D600635494 /* PlaylistApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistApi.swift; sourceTree = "<group>"; };
|
E97AF45F23FC85D600635494 /* PlaylistApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistApi.swift; sourceTree = "<group>"; };
|
||||||
@ -192,6 +194,7 @@
|
|||||||
E9E30C2923FEAA3A00574EEF /* TagRow.swift */,
|
E9E30C2923FEAA3A00574EEF /* TagRow.swift */,
|
||||||
E9E30C2C23FEAB0200574EEF /* TagView.swift */,
|
E9E30C2C23FEAB0200574EEF /* TagView.swift */,
|
||||||
E9E30C3023FEAF2B00574EEF /* TagObjList.swift */,
|
E9E30C3023FEAF2B00574EEF /* TagObjList.swift */,
|
||||||
|
E934AC98240DD0E4009869F4 /* AddTagSheet.swift */,
|
||||||
);
|
);
|
||||||
path = Tag;
|
path = Tag;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -409,6 +412,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
E934AC99240DD0E4009869F4 /* AddTagSheet.swift in Sources */,
|
||||||
E9E30C2D23FEAB0200574EEF /* TagView.swift in Sources */,
|
E9E30C2D23FEAB0200574EEF /* TagView.swift in Sources */,
|
||||||
E9E30C2823FEA6BD00574EEF /* Tag.swift in Sources */,
|
E9E30C2823FEA6BD00574EEF /* Tag.swift in Sources */,
|
||||||
E9E30C3123FEAF2B00574EEF /* TagObjList.swift in Sources */,
|
E9E30C3123FEAF2B00574EEF /* TagObjList.swift in Sources */,
|
||||||
|
@ -22,12 +22,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||||||
// Create the SwiftUI view that provides the window contents.
|
// Create the SwiftUI view that provides the window contents.
|
||||||
let contentView = RootView()
|
let contentView = RootView()
|
||||||
|
|
||||||
let liveUser = LiveUser(playlists: [], tags: [])
|
|
||||||
|
|
||||||
let keychain = Keychain(service: "xyz.sarsoo.music.login")
|
let keychain = Keychain(service: "xyz.sarsoo.music.login")
|
||||||
|
|
||||||
// debugPrint(keychain["username"] ?? "no username")
|
|
||||||
// debugPrint(keychain["password"] ?? "no password")
|
|
||||||
|
|
||||||
// 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 {
|
||||||
@ -35,6 +30,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||||||
|
|
||||||
var controller: UIViewController
|
var controller: UIViewController
|
||||||
if keychain["username"] != nil && keychain["password"] != nil {
|
if keychain["username"] != nil && keychain["password"] != nil {
|
||||||
|
let liveUser = LiveUser(playlists: [], tags: [], username: keychain["username"]!)
|
||||||
controller = UIHostingController(rootView: contentView.environmentObject(liveUser))
|
controller = UIHostingController(rootView: contentView.environmentObject(liveUser))
|
||||||
} else {
|
} else {
|
||||||
let storyboard = UIStoryboard(name: "Main", bundle: nil)
|
let storyboard = UIStoryboard(name: "Main", bundle: nil)
|
||||||
|
21
Music Tools/Assets.xcassets/APFooter.imageset/Contents.json
vendored
Normal file
21
Music Tools/Assets.xcassets/APFooter.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "ap.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
BIN
Music Tools/Assets.xcassets/APFooter.imageset/ap.png
vendored
Normal file
BIN
Music Tools/Assets.xcassets/APFooter.imageset/ap.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 273 KiB |
@ -27,7 +27,8 @@ class LoginController: UIViewController, UITextFieldDelegate {
|
|||||||
|
|
||||||
// MARK: Actions
|
// MARK: Actions
|
||||||
@IBSegueAction func returnUIView(_ coder: NSCoder) -> UIViewController? {
|
@IBSegueAction func returnUIView(_ coder: NSCoder) -> UIViewController? {
|
||||||
let liveUser = LiveUser(playlists: [], tags: [])
|
// TODO add right username
|
||||||
|
let liveUser = LiveUser(playlists: [], tags: [], username: "")
|
||||||
return UIHostingController(coder: coder, rootView: RootView().environmentObject(liveUser))
|
return UIHostingController(coder: coder, rootView: RootView().environmentObject(liveUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,10 +12,12 @@ class LiveUser: ObservableObject {
|
|||||||
|
|
||||||
@Published var playlists: [Playlist]
|
@Published var playlists: [Playlist]
|
||||||
@Published var tags: [Tag]
|
@Published var tags: [Tag]
|
||||||
|
@Published var username: String
|
||||||
|
|
||||||
init(playlists: [Playlist], tags: [Tag]) {
|
init(playlists: [Playlist], tags: [Tag], username: String) {
|
||||||
self.playlists = playlists
|
self.playlists = playlists
|
||||||
self.tags = tags
|
self.tags = tags
|
||||||
|
self.username = username
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatePlaylist(playlistIn: Playlist) {
|
func updatePlaylist(playlistIn: Playlist) {
|
||||||
|
@ -16,6 +16,7 @@ public enum TagApi {
|
|||||||
case updateTag(tag_id: String, updates: JSON)
|
case updateTag(tag_id: String, updates: JSON)
|
||||||
case deleteTag(tag_id: String)
|
case deleteTag(tag_id: String)
|
||||||
case newTag(tag_id: String)
|
case newTag(tag_id: String)
|
||||||
|
case getTag(tag_id: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TagApi: ApiRequest {
|
extension TagApi: ApiRequest {
|
||||||
@ -35,6 +36,8 @@ extension TagApi: ApiRequest {
|
|||||||
return "api/tag/\(tag_id)"
|
return "api/tag/\(tag_id)"
|
||||||
case .newTag(let tag_id):
|
case .newTag(let tag_id):
|
||||||
return "api/tag/\(tag_id)"
|
return "api/tag/\(tag_id)"
|
||||||
|
case .getTag(let tag_id):
|
||||||
|
return "api/tag/\(tag_id)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,6 +53,8 @@ extension TagApi: ApiRequest {
|
|||||||
return .delete
|
return .delete
|
||||||
case .newTag:
|
case .newTag:
|
||||||
return .post
|
return .post
|
||||||
|
case .getTag:
|
||||||
|
return .get
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,12 +64,14 @@ extension TagApi: ApiRequest {
|
|||||||
return nil
|
return nil
|
||||||
case .runTag:
|
case .runTag:
|
||||||
return nil
|
return nil
|
||||||
case .updateTag(let tag_id, let updates):
|
case .updateTag(let _, let updates):
|
||||||
return updates
|
return updates
|
||||||
case .deleteTag:
|
case .deleteTag:
|
||||||
return nil
|
return nil
|
||||||
case .newTag:
|
case .newTag:
|
||||||
return nil
|
return nil
|
||||||
|
case .getTag:
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,6 +87,8 @@ extension TagApi: ApiRequest {
|
|||||||
return nil
|
return nil
|
||||||
case .newTag:
|
case .newTag:
|
||||||
return nil
|
return nil
|
||||||
|
case .getTag:
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ struct AddPlaylistSheet: View {
|
|||||||
|
|
||||||
@Binding var state: Bool
|
@Binding var state: Bool
|
||||||
@Binding var playlists: Array<Playlist>
|
@Binding var playlists: Array<Playlist>
|
||||||
|
@Binding var username: String
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
@ -73,10 +74,26 @@ struct AddPlaylistSheet: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var playlist: Playlist? = nil
|
||||||
|
switch PlaylistType(rawValue: selectedType) ?? .defaultPlaylist {
|
||||||
|
case .defaultPlaylist:
|
||||||
|
playlist = Playlist(name: name, uri: "", username: username, include_recommendations: false, recommendation_sample: 10, include_library_tracks: false, parts: [], playlist_references: [], shuffle: false)
|
||||||
|
break
|
||||||
|
case .recents:
|
||||||
|
playlist = RecentsPlaylist(name: name, uri: "", username: username, include_recommendations: false, recommendation_sample: 10, include_library_tracks: false, parts: [], playlist_references: [], shuffle: false, add_last_month: false, add_this_month: false, day_boundary: 14)
|
||||||
|
break
|
||||||
|
case .fmchart:
|
||||||
|
playlist = LastFMChartPlaylist(name: name, uri: "", username: username, include_recommendations: false, recommendation_sample: 10, include_library_tracks: false, parts: [], playlist_references: [], shuffle: false, chart_range: .month, chart_limit: 10)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
isLoading = true
|
isLoading = true
|
||||||
let api = PlaylistApi.newPlaylist(name: self.name,
|
let api = PlaylistApi.newPlaylist(name: self.name,
|
||||||
type: PlaylistType(rawValue: selectedType) ?? .defaultPlaylist)
|
type: PlaylistType(rawValue: selectedType) ?? .defaultPlaylist)
|
||||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
|
self.playlists.append(playlist!)
|
||||||
|
self.playlists = self.playlists.sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
|
||||||
|
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
self.state = false
|
self.state = false
|
||||||
}
|
}
|
||||||
@ -85,6 +102,6 @@ struct AddPlaylistSheet: View {
|
|||||||
|
|
||||||
struct AddPlaylistSheet_Previews: PreviewProvider {
|
struct AddPlaylistSheet_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
AddPlaylistSheet(state: .constant(true), playlists: .constant([]))
|
AddPlaylistSheet(state: .constant(true), playlists: .constant([]), username: .constant("username"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,13 +10,10 @@ import SwiftUI
|
|||||||
import SwiftyJSON
|
import SwiftyJSON
|
||||||
|
|
||||||
struct PlaylistRow: View {
|
struct PlaylistRow: View {
|
||||||
|
@Binding var playlist: Playlist
|
||||||
@EnvironmentObject var liveUser: LiveUser
|
|
||||||
|
|
||||||
var playlist: Playlist
|
|
||||||
|
|
||||||
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 {
|
.contextMenu {
|
||||||
@ -50,18 +47,7 @@ struct PlaylistRow: View {
|
|||||||
struct PlaylistRow_Previews: PreviewProvider {
|
struct PlaylistRow_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
PlaylistRow(playlist:
|
PlaylistRow(playlist:
|
||||||
Playlist(name: "playlist name",
|
.constant(Playlist(name: "", uri: "", username: "", include_recommendations: true, recommendation_sample: 1, include_library_tracks: true, parts: [], playlist_references: [], shuffle: true))
|
||||||
uri: "uri",
|
|
||||||
username: "username",
|
|
||||||
|
|
||||||
include_recommendations: true,
|
|
||||||
recommendation_sample: 5,
|
|
||||||
include_library_tracks: true,
|
|
||||||
|
|
||||||
parts: ["name"],
|
|
||||||
playlist_references: ["ref name"],
|
|
||||||
|
|
||||||
shuffle: true)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,41 +7,14 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
//import SwiftUIRefresh
|
import SwiftUIRefresh
|
||||||
import SwiftyJSON
|
import SwiftyJSON
|
||||||
|
|
||||||
final class ChangeableBool: ObservableObject {
|
|
||||||
|
|
||||||
var onClick: () -> ()
|
|
||||||
|
|
||||||
init(onClick: @escaping () -> ()) {
|
|
||||||
self.onClick = onClick
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var state: Bool = false {
|
|
||||||
didSet {
|
|
||||||
self.onClick()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PlaylistView: View {
|
struct PlaylistView: View {
|
||||||
|
|
||||||
@EnvironmentObject var liveUser: LiveUser
|
@EnvironmentObject var liveUser: LiveUser
|
||||||
|
|
||||||
init(playlist: Playlist) {
|
@Binding var playlist: Playlist
|
||||||
self.playlist = playlist
|
|
||||||
|
|
||||||
// hide empty items below list
|
|
||||||
UITableView.appearance().tableFooterView = UIView()
|
|
||||||
}
|
|
||||||
|
|
||||||
var playlist: Playlist
|
|
||||||
@State private var recommendations: Bool = true
|
|
||||||
@State private var library_Tracks: Bool = false
|
|
||||||
@State private var shuffle: Bool = false
|
|
||||||
|
|
||||||
@State private var rec_num: Int = 0
|
|
||||||
|
|
||||||
@State private var this_month: Bool = false
|
@State private var this_month: Bool = false
|
||||||
@State private var last_month: Bool = false
|
@State private var last_month: Bool = false
|
||||||
@ -55,33 +28,33 @@ struct PlaylistView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
List {
|
||||||
Section(header: Text("Options")){
|
Section(header: Text("Options")){
|
||||||
Toggle(isOn: $recommendations) {
|
Toggle(isOn: self.$playlist.include_recommendations) {
|
||||||
Text("Spotify Recommendations")
|
Text("Spotify Recommendations")
|
||||||
}
|
}
|
||||||
|
|
||||||
// if recommendations {
|
if self.playlist.include_recommendations {
|
||||||
Stepper(onIncrement: {
|
Stepper(onIncrement: {
|
||||||
self.$rec_num.wrappedValue += 1
|
self.$playlist.recommendation_sample.wrappedValue += 1
|
||||||
self.updatePlaylist(updates: JSON(["recommendation_sample": self.$rec_num.wrappedValue]))
|
self.updatePlaylist(updates: JSON(["recommendation_sample": self.playlist.recommendation_sample]))
|
||||||
},
|
},
|
||||||
onDecrement: {
|
onDecrement: {
|
||||||
self.$rec_num.wrappedValue -= 1
|
self.$playlist.recommendation_sample.wrappedValue -= 1
|
||||||
self.updatePlaylist(updates: JSON(["recommendation_sample": self.$rec_num.wrappedValue]))
|
self.updatePlaylist(updates: JSON(["recommendation_sample": self.playlist.recommendation_sample]))
|
||||||
}){
|
}){
|
||||||
Text("#:")
|
Text("#:")
|
||||||
.foregroundColor(Color.gray)
|
.foregroundColor(Color.gray)
|
||||||
.multilineTextAlignment(.trailing)
|
.multilineTextAlignment(.trailing)
|
||||||
Text("\(rec_num)")
|
Text("\(self.playlist.recommendation_sample)")
|
||||||
.multilineTextAlignment(.trailing)
|
.multilineTextAlignment(.trailing)
|
||||||
|
|
||||||
}
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
Toggle(isOn: $library_Tracks) {
|
Toggle(isOn: self.$playlist.include_library_tracks) {
|
||||||
Text("Library Tracks")
|
Text("Library Tracks")
|
||||||
}
|
}
|
||||||
|
|
||||||
Toggle(isOn: $shuffle) {
|
Toggle(isOn: self.$playlist.shuffle) {
|
||||||
Text("Shuffle")
|
Text("Shuffle")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,17 +120,13 @@ struct PlaylistView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// .pullToRefresh(isShowing: $isRefreshing) {
|
.pullToRefresh(isShowing: $isRefreshing) {
|
||||||
// self.refreshPlaylist()
|
self.refreshPlaylist()
|
||||||
// }
|
}
|
||||||
.navigationBarTitle(Text(playlist.name))
|
.navigationBarTitle(Text(playlist.name))
|
||||||
.onAppear {
|
.onAppear {
|
||||||
self.$recommendations.wrappedValue = self.playlist.include_recommendations
|
|
||||||
self.$library_Tracks.wrappedValue = self.playlist.include_library_tracks
|
|
||||||
self.$shuffle.wrappedValue = self.playlist.shuffle
|
|
||||||
|
|
||||||
self.$rec_num.wrappedValue = self.playlist.recommendation_sample
|
|
||||||
|
|
||||||
|
// TODO are these binding properly?
|
||||||
if let playlist = self.playlist as? RecentsPlaylist {
|
if let playlist = self.playlist as? RecentsPlaylist {
|
||||||
self.$this_month.wrappedValue = playlist.add_this_month
|
self.$this_month.wrappedValue = playlist.add_this_month
|
||||||
self.$last_month.wrappedValue = playlist.add_last_month
|
self.$last_month.wrappedValue = playlist.add_last_month
|
||||||
@ -204,7 +173,7 @@ struct PlaylistView: View {
|
|||||||
//TODO: do better error checking
|
//TODO: do better error checking
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshPlaylist(updates: JSON) {
|
func refreshPlaylist() {
|
||||||
let api = PlaylistApi.getPlaylist(name: self.playlist.name)
|
let api = PlaylistApi.getPlaylist(name: self.playlist.name)
|
||||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
guard let data = response.data else {
|
guard let data = response.data else {
|
||||||
@ -214,11 +183,8 @@ struct PlaylistView: View {
|
|||||||
guard let json = try? JSON(data: data) else {
|
guard let json = try? JSON(data: data) else {
|
||||||
fatalError("error parsing reponse")
|
fatalError("error parsing reponse")
|
||||||
}
|
}
|
||||||
|
self.playlist = Playlist.fromDict(dictionary: json)!
|
||||||
// let playlist = Playlist.fromDict(json["playlist"])
|
self.isRefreshing = false
|
||||||
//
|
|
||||||
// self.playlist = playlist
|
|
||||||
// self.isRefreshing = false
|
|
||||||
}
|
}
|
||||||
//TODO: do better error checking
|
//TODO: do better error checking
|
||||||
}
|
}
|
||||||
@ -226,7 +192,7 @@ struct PlaylistView: View {
|
|||||||
|
|
||||||
struct PlaylistView_Previews: PreviewProvider {
|
struct PlaylistView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
PlaylistView(playlist:
|
PlaylistView(playlist: .constant(
|
||||||
Playlist(name: "playlist name",
|
Playlist(name: "playlist name",
|
||||||
uri: "uri",
|
uri: "uri",
|
||||||
username: "username",
|
username: "username",
|
||||||
@ -239,6 +205,6 @@ struct PlaylistView_Previews: PreviewProvider {
|
|||||||
playlist_references: ["ref name"],
|
playlist_references: ["ref name"],
|
||||||
|
|
||||||
shuffle: true)
|
shuffle: true)
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,14 +19,8 @@ struct RootView: View {
|
|||||||
|
|
||||||
@State private var showAdd = false // State for showing add modal view
|
@State private var showAdd = false // State for showing add modal view
|
||||||
|
|
||||||
@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 isRefreshingPlaylists = false
|
@State private var isRefreshingPlaylists = false
|
||||||
@State private var isRefreshingTags = false
|
@State private var isRefreshingTags = false
|
||||||
|
|
||||||
// refresh playlist list on interval
|
|
||||||
// let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect()
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
TabView {
|
TabView {
|
||||||
@ -34,15 +28,12 @@ struct RootView: View {
|
|||||||
// PLAYLISTS
|
// PLAYLISTS
|
||||||
NavigationView {
|
NavigationView {
|
||||||
List{
|
List{
|
||||||
ForEach(liveUser.playlists) { playlist in
|
ForEach(liveUser.playlists.indices, id:\.self) { idx in
|
||||||
PlaylistRow(playlist: playlist)
|
PlaylistRow(playlist: self.$liveUser.playlists[idx])
|
||||||
}
|
}
|
||||||
.onDelete { indexSet in
|
.onDelete { indexSet in
|
||||||
|
|
||||||
indexSet.forEach { index in
|
indexSet.forEach { index in
|
||||||
// add to recently deleted playlist cache
|
|
||||||
self.justDeletedPlaylists.append(self.liveUser.playlists[index])
|
|
||||||
|
|
||||||
let api = PlaylistApi.deletePlaylist(name: self.liveUser.playlists[index].name)
|
let api = PlaylistApi.deletePlaylist(name: self.liveUser.playlists[index].name)
|
||||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
|
|
||||||
@ -63,7 +54,7 @@ struct RootView: View {
|
|||||||
action: { self.showAdd = true },
|
action: { self.showAdd = true },
|
||||||
label: { Text("Add") }
|
label: { Text("Add") }
|
||||||
).sheet(isPresented: $showAdd) {
|
).sheet(isPresented: $showAdd) {
|
||||||
AddPlaylistSheet(state: self.$showAdd, playlists: self.$liveUser.playlists)
|
AddPlaylistSheet(state: self.$showAdd, playlists: self.$liveUser.playlists, username: self.$liveUser.username)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -78,15 +69,12 @@ struct RootView: View {
|
|||||||
// TAGS
|
// TAGS
|
||||||
NavigationView {
|
NavigationView {
|
||||||
List{
|
List{
|
||||||
ForEach(liveUser.tags) { tag in
|
ForEach(liveUser.tags.indices, id:\.self) { idx in
|
||||||
TagRow(tag: tag)
|
TagRow(tag: self.$liveUser.tags[idx])
|
||||||
}
|
}
|
||||||
.onDelete { indexSet in
|
.onDelete { indexSet in
|
||||||
|
|
||||||
indexSet.forEach { index in
|
indexSet.forEach { index in
|
||||||
// add to recently deleted playlist cache
|
|
||||||
self.justDeletedTags.append(self.liveUser.tags[index])
|
|
||||||
|
|
||||||
let api = TagApi.deleteTag(tag_id: self.liveUser.tags[index].tag_id)
|
let api = TagApi.deleteTag(tag_id: self.liveUser.tags[index].tag_id)
|
||||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
|
|
||||||
@ -107,7 +95,7 @@ struct RootView: View {
|
|||||||
action: { self.showAdd = true },
|
action: { self.showAdd = true },
|
||||||
label: { Text("Add") }
|
label: { Text("Add") }
|
||||||
).sheet(isPresented: $showAdd) {
|
).sheet(isPresented: $showAdd) {
|
||||||
AddPlaylistSheet(state: self.$showAdd, playlists: self.$liveUser.playlists)
|
AddTagSheet(state: self.$showAdd, tags: self.$liveUser.tags, username: self.$liveUser.username)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -130,9 +118,6 @@ struct RootView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.tag(2)
|
.tag(2)
|
||||||
// .onReceive(timer) { _ in
|
|
||||||
// self.fetch()
|
|
||||||
// }
|
|
||||||
}.onAppear {
|
}.onAppear {
|
||||||
self.fetchAll()
|
self.fetchAll()
|
||||||
}
|
}
|
||||||
@ -143,7 +128,7 @@ struct RootView: View {
|
|||||||
refreshTags()
|
refreshTags()
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshPlaylists() {
|
public func refreshPlaylists() {
|
||||||
let api = PlaylistApi.getPlaylists
|
let api = PlaylistApi.getPlaylists
|
||||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
|
|
||||||
@ -162,19 +147,6 @@ struct RootView: View {
|
|||||||
})
|
})
|
||||||
// sort
|
// sort
|
||||||
.sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
|
.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.justDeletedPlaylists {
|
|
||||||
if playlist == rxPlaylist {
|
|
||||||
deleted = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return !deleted
|
|
||||||
}
|
|
||||||
// clear cache of recently deleted playlists
|
|
||||||
self.justDeletedPlaylists = []
|
|
||||||
|
|
||||||
// update state
|
// update state
|
||||||
self.liveUser.playlists = playlists
|
self.liveUser.playlists = playlists
|
||||||
@ -183,7 +155,7 @@ struct RootView: View {
|
|||||||
//TODO: do better error checking
|
//TODO: do better error checking
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshTags() {
|
public func refreshTags() {
|
||||||
let tagApi = TagApi.getTags
|
let tagApi = TagApi.getTags
|
||||||
RequestBuilder.buildRequest(apiRequest: tagApi).responseJSON{ response in
|
RequestBuilder.buildRequest(apiRequest: tagApi).responseJSON{ response in
|
||||||
|
|
||||||
@ -202,19 +174,6 @@ struct RootView: View {
|
|||||||
})
|
})
|
||||||
// sort
|
// sort
|
||||||
.sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
|
.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
|
// update state
|
||||||
self.liveUser.tags = tags
|
self.liveUser.tags = tags
|
||||||
|
@ -10,8 +10,21 @@ import SwiftUI
|
|||||||
import KeychainAccess
|
import KeychainAccess
|
||||||
|
|
||||||
struct SettingsList: View {
|
struct SettingsList: View {
|
||||||
|
|
||||||
|
init(){
|
||||||
|
UITableView.appearance().tableFooterView = UIView()
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
VStack{
|
||||||
List{
|
List{
|
||||||
|
Button(action: {
|
||||||
|
if let url = URL(string: "https://music.sarsoo.xyz") {
|
||||||
|
UIApplication.shared.open(url)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text("Open Web")
|
||||||
|
}
|
||||||
Button(action: {
|
Button(action: {
|
||||||
let keychain = Keychain(service: "xyz.sarsoo.music.login")
|
let keychain = Keychain(service: "xyz.sarsoo.music.login")
|
||||||
do {
|
do {
|
||||||
@ -24,6 +37,11 @@ struct SettingsList: View {
|
|||||||
Text("Log out")
|
Text("Log out")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Image("APFooter")
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(width: 100.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
90
Music Tools/Views/Tag/AddTagSheet.swift
Normal file
90
Music Tools/Views/Tag/AddTagSheet.swift
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
//
|
||||||
|
// AddTagSheet.swift
|
||||||
|
// Music Tools
|
||||||
|
//
|
||||||
|
// Created by Andy Pack on 02/03/2020.
|
||||||
|
// Copyright © 2020 Sarsoo. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import SwiftyJSON
|
||||||
|
|
||||||
|
struct AddTagSheet: View {
|
||||||
|
|
||||||
|
@State private var name = ""
|
||||||
|
@State private var errorMessage = ""
|
||||||
|
@State private var isLoading = false
|
||||||
|
|
||||||
|
@Binding var state: Bool
|
||||||
|
@Binding var tags: Array<Tag>
|
||||||
|
@Binding var username: String
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
Text("New Tag")
|
||||||
|
.font(.largeTitle)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.padding([.top, .leading, .trailing], 20.0)
|
||||||
|
|
||||||
|
}
|
||||||
|
TextField("Name", text: $name)
|
||||||
|
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||||
|
.padding([.bottom, .leading, .trailing], 20.0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Button(action: create){
|
||||||
|
Text("Add")
|
||||||
|
.font(.title)
|
||||||
|
}
|
||||||
|
.disabled(isLoading)
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
Text(errorMessage)
|
||||||
|
.foregroundColor(Color.red)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
|
||||||
|
}
|
||||||
|
|
||||||
|
func create(){
|
||||||
|
debugPrint(name)
|
||||||
|
let tag_id = self.$name.wrappedValue.replacingOccurrences(of: " ", with: "_")
|
||||||
|
|
||||||
|
if tag_id.count == 0 {
|
||||||
|
errorMessage = "Enter Tag Name"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var tagPresent = false
|
||||||
|
for tag in tags {
|
||||||
|
if tag.tag_id == tag_id {
|
||||||
|
tagPresent = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tagPresent == true {
|
||||||
|
errorMessage = "Tag already created"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let tag = Tag(tag_id: tag_id, name: name, username: self.username, tracks: [], albums: [], artists: [], count: 0, proportion: 0.0, total_user_scrobbles: 0, last_updated: "Never")
|
||||||
|
|
||||||
|
isLoading = true
|
||||||
|
let api = TagApi.newTag(tag_id: tag_id)
|
||||||
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
|
self.tags.append(tag)
|
||||||
|
self.tags = self.tags.sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
|
||||||
|
|
||||||
|
self.isLoading = false
|
||||||
|
self.state = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AddTagSheet_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
AddTagSheet(state: .constant(true), tags: .constant([]), username: .constant("username"))
|
||||||
|
}
|
||||||
|
}
|
@ -11,12 +11,10 @@ import SwiftyJSON
|
|||||||
|
|
||||||
struct TagRow: View {
|
struct TagRow: View {
|
||||||
|
|
||||||
@EnvironmentObject var liveUser: LiveUser
|
@Binding var tag: Tag
|
||||||
|
|
||||||
var tag: Tag
|
|
||||||
|
|
||||||
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 {
|
.contextMenu {
|
||||||
@ -39,7 +37,7 @@ struct TagRow: View {
|
|||||||
|
|
||||||
struct TagRow_Previews: PreviewProvider {
|
struct TagRow_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
TagRow(tag:
|
TagRow(tag: .constant(
|
||||||
Tag(tag_id: "tag_id",
|
Tag(tag_id: "tag_id",
|
||||||
name: "tag name",
|
name: "tag name",
|
||||||
username: "andy",
|
username: "andy",
|
||||||
@ -53,6 +51,6 @@ struct TagRow_Previews: PreviewProvider {
|
|||||||
total_user_scrobbles: 2000,
|
total_user_scrobbles: 2000,
|
||||||
|
|
||||||
last_updated: "10th Feb")
|
last_updated: "10th Feb")
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,18 +7,14 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import SwiftUIRefresh
|
||||||
import SwiftyJSON
|
import SwiftyJSON
|
||||||
|
|
||||||
struct TagView: View {
|
struct TagView: View {
|
||||||
|
|
||||||
init(tag: Tag) {
|
@Binding var tag: Tag
|
||||||
self.tag = tag
|
|
||||||
|
|
||||||
// hide empty items below list
|
|
||||||
UITableView.appearance().tableFooterView = UIView()
|
|
||||||
}
|
|
||||||
|
|
||||||
var tag: Tag
|
@State private var isRefreshing = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List {
|
List {
|
||||||
@ -82,6 +78,9 @@ struct TagView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.pullToRefresh(isShowing: $isRefreshing) {
|
||||||
|
self.refreshTag()
|
||||||
|
}
|
||||||
.navigationBarTitle(Text(tag.name))
|
.navigationBarTitle(Text(tag.name))
|
||||||
.onAppear {
|
.onAppear {
|
||||||
|
|
||||||
@ -103,11 +102,27 @@ struct TagView: View {
|
|||||||
}
|
}
|
||||||
//TODO: do better error checking
|
//TODO: do better error checking
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func refreshTag() {
|
||||||
|
let api = TagApi.getTag(tag_id: self.tag.tag_id)
|
||||||
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
|
guard let data = response.data else {
|
||||||
|
fatalError("error getting tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let json = try? JSON(data: data) else {
|
||||||
|
fatalError("error parsing reponse")
|
||||||
|
}
|
||||||
|
self.tag = Tag.fromDict(dictionary: json["tag"])
|
||||||
|
self.isRefreshing = false
|
||||||
|
}
|
||||||
|
//TODO: do better error checking
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TagView_Previews: PreviewProvider {
|
struct TagView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
TagView(tag:
|
TagView(tag: .constant(
|
||||||
Tag(tag_id: "tag_id",
|
Tag(tag_id: "tag_id",
|
||||||
name: "tag name",
|
name: "tag name",
|
||||||
username: "andy",
|
username: "andy",
|
||||||
@ -121,6 +136,6 @@ struct TagView_Previews: PreviewProvider {
|
|||||||
total_user_scrobbles: 2000,
|
total_user_scrobbles: 2000,
|
||||||
|
|
||||||
last_updated: "10th Feb")
|
last_updated: "10th Feb")
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user