added admin page, better net calls, toasts
This commit is contained in:
parent
7477ff0a75
commit
9b59baa8f1
@ -9,6 +9,7 @@
|
|||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
A10C8D29281302050018AE12 /* ToastUI in Frameworks */ = {isa = PBXBuildFile; productRef = A10C8D28281302050018AE12 /* ToastUI */; };
|
A10C8D29281302050018AE12 /* ToastUI in Frameworks */ = {isa = PBXBuildFile; productRef = A10C8D28281302050018AE12 /* ToastUI */; };
|
||||||
A11AC70628A188AE00645043 /* AuthApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11AC70528A188AE00645043 /* AuthApi.swift */; };
|
A11AC70628A188AE00645043 /* AuthApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = A11AC70528A188AE00645043 /* AuthApi.swift */; };
|
||||||
|
A1DBCDA628A51869002CF730 /* AdminList.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1DBCDA528A51869002CF730 /* AdminList.swift */; };
|
||||||
E906F7F42414019C004E1E31 /* NetworkPersister.swift in Sources */ = {isa = PBXBuildFile; fileRef = E906F7F32414019C004E1E31 /* NetworkPersister.swift */; };
|
E906F7F42414019C004E1E31 /* NetworkPersister.swift in Sources */ = {isa = PBXBuildFile; fileRef = E906F7F32414019C004E1E31 /* NetworkPersister.swift */; };
|
||||||
E906F7F724143AA7004E1E31 /* SwiftUICharts in Frameworks */ = {isa = PBXBuildFile; productRef = E906F7F624143AA7004E1E31 /* SwiftUICharts */; };
|
E906F7F724143AA7004E1E31 /* SwiftUICharts in Frameworks */ = {isa = PBXBuildFile; productRef = E906F7F624143AA7004E1E31 /* SwiftUICharts */; };
|
||||||
E934AC99240DD0E4009869F4 /* AddTagSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E934AC98240DD0E4009869F4 /* AddTagSheet.swift */; };
|
E934AC99240DD0E4009869F4 /* AddTagSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E934AC98240DD0E4009869F4 /* AddTagSheet.swift */; };
|
||||||
@ -65,6 +66,7 @@
|
|||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
A11AC70528A188AE00645043 /* AuthApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthApi.swift; sourceTree = "<group>"; };
|
A11AC70528A188AE00645043 /* AuthApi.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthApi.swift; sourceTree = "<group>"; };
|
||||||
A146915A28118F940052999D /* Mixonomer.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Mixonomer.entitlements; sourceTree = "<group>"; };
|
A146915A28118F940052999D /* Mixonomer.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Mixonomer.entitlements; sourceTree = "<group>"; };
|
||||||
|
A1DBCDA528A51869002CF730 /* AdminList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminList.swift; sourceTree = "<group>"; };
|
||||||
E906F7F32414019C004E1E31 /* NetworkPersister.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkPersister.swift; sourceTree = "<group>"; };
|
E906F7F32414019C004E1E31 /* NetworkPersister.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkPersister.swift; sourceTree = "<group>"; };
|
||||||
E934AC98240DD0E4009869F4 /* AddTagSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTagSheet.swift; sourceTree = "<group>"; };
|
E934AC98240DD0E4009869F4 /* AddTagSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTagSheet.swift; sourceTree = "<group>"; };
|
||||||
E971F8B8245462D500B543B6 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
|
E971F8B8245462D500B543B6 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
|
||||||
@ -133,6 +135,14 @@
|
|||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
A1DBCDA428A5184D002CF730 /* Admin */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
A1DBCDA528A51869002CF730 /* AdminList.swift */,
|
||||||
|
);
|
||||||
|
path = Admin;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
E98254BE23F9BD540056D9D3 /* Model */ = {
|
E98254BE23F9BD540056D9D3 /* Model */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@ -160,6 +170,7 @@
|
|||||||
E98254C023F9FFDD0056D9D3 /* Views */ = {
|
E98254C023F9FFDD0056D9D3 /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
A1DBCDA428A5184D002CF730 /* Admin */,
|
||||||
E9E30C3423FF256100574EEF /* Settings */,
|
E9E30C3423FF256100574EEF /* Settings */,
|
||||||
E9E30C2F23FEACF700574EEF /* Tag */,
|
E9E30C2F23FEACF700574EEF /* Tag */,
|
||||||
E9E30C2E23FEACDE00574EEF /* Playlist */,
|
E9E30C2E23FEACDE00574EEF /* Playlist */,
|
||||||
@ -431,6 +442,7 @@
|
|||||||
E9CCD5BD2454C64300B5CD6C /* TagList.swift in Sources */,
|
E9CCD5BD2454C64300B5CD6C /* TagList.swift in Sources */,
|
||||||
E9E30C2A23FEAA3A00574EEF /* TagRow.swift in Sources */,
|
E9E30C2A23FEAA3A00574EEF /* TagRow.swift in Sources */,
|
||||||
E9CCD5BB2454C57300B5CD6C /* PlaylistList.swift in Sources */,
|
E9CCD5BB2454C57300B5CD6C /* PlaylistList.swift in Sources */,
|
||||||
|
A1DBCDA628A51869002CF730 /* AdminList.swift in Sources */,
|
||||||
E97AF46023FC85D600635494 /* PlaylistApi.swift in Sources */,
|
E97AF46023FC85D600635494 /* PlaylistApi.swift in Sources */,
|
||||||
A11AC70628A188AE00645043 /* AuthApi.swift in Sources */,
|
A11AC70628A188AE00645043 /* AuthApi.swift in Sources */,
|
||||||
E9EA690F23F9A5430012C3E8 /* AppSkeleton.swift in Sources */,
|
E9EA690F23F9A5430012C3E8 /* AppSkeleton.swift in Sources */,
|
||||||
|
@ -15,6 +15,7 @@ class LiveUser: ObservableObject {
|
|||||||
@Published var playlists: [Playlist]
|
@Published var playlists: [Playlist]
|
||||||
@Published var tags: [Tag]
|
@Published var tags: [Tag]
|
||||||
@Published var username: String
|
@Published var username: String
|
||||||
|
@Published var user: User?
|
||||||
|
|
||||||
@Published var loggedIn: Bool {
|
@Published var loggedIn: Bool {
|
||||||
didSet {
|
didSet {
|
||||||
@ -22,6 +23,7 @@ class LiveUser: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Published var isRefreshingUser = false
|
||||||
@Published var isRefreshingPlaylists = false
|
@Published var isRefreshingPlaylists = false
|
||||||
@Published var isRefreshingTags = false
|
@Published var isRefreshingTags = false
|
||||||
|
|
||||||
@ -39,16 +41,47 @@ class LiveUser: ObservableObject {
|
|||||||
self.playlists[index] = playlistIn
|
self.playlists[index] = playlistIn
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshPlaylists() {
|
func refreshUser(onSuccess: (() -> Void)? = nil, onFailure: (() -> Void)? = nil) {
|
||||||
|
self.isRefreshingUser = true
|
||||||
|
|
||||||
|
let api = UserApi.getUser
|
||||||
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
|
|
||||||
|
if self.checkNetworkResponse(response: response) {
|
||||||
|
|
||||||
|
guard let data = response.data else {
|
||||||
|
fatalError("error getting user")
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let json = try? JSON(data: data) else {
|
||||||
|
fatalError("error parsing user")
|
||||||
|
}
|
||||||
|
|
||||||
|
// update state
|
||||||
|
self.user = UserApi.fromJSON(user: json)
|
||||||
|
|
||||||
|
self.isRefreshingUser = false
|
||||||
|
|
||||||
|
if let success = onSuccess {
|
||||||
|
success()
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
if let failure = onFailure {
|
||||||
|
failure()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func refreshPlaylists(onSuccess: (() -> Void)? = nil, onFailure: (() -> Void)? = nil) {
|
||||||
self.isRefreshingPlaylists = true
|
self.isRefreshingPlaylists = true
|
||||||
|
|
||||||
let api = PlaylistApi.getPlaylists
|
let api = PlaylistApi.getPlaylists
|
||||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
|
|
||||||
self.checkNetworkResponse(response: response)
|
if self.checkNetworkResponse(response: response) {
|
||||||
|
|
||||||
switch response.response?.statusCode {
|
|
||||||
case 200, 201:
|
|
||||||
|
|
||||||
guard let data = response.data else {
|
guard let data = response.data else {
|
||||||
fatalError("error getting playlists")
|
fatalError("error getting playlists")
|
||||||
@ -65,6 +98,10 @@ class LiveUser: ObservableObject {
|
|||||||
|
|
||||||
self.isRefreshingPlaylists = false
|
self.isRefreshingPlaylists = false
|
||||||
|
|
||||||
|
if let success = onSuccess {
|
||||||
|
success()
|
||||||
|
}
|
||||||
|
|
||||||
let encoder = JSONEncoder()
|
let encoder = JSONEncoder()
|
||||||
do {
|
do {
|
||||||
UserDefaults.standard.set(String(data: try encoder.encode(playlists), encoding: .utf8), forKey: "playlists")
|
UserDefaults.standard.set(String(data: try encoder.encode(playlists), encoding: .utf8), forKey: "playlists")
|
||||||
@ -72,22 +109,22 @@ class LiveUser: ObservableObject {
|
|||||||
print("error encoding playlists: \(error)")
|
print("error encoding playlists: \(error)")
|
||||||
}
|
}
|
||||||
|
|
||||||
case _:
|
} else {
|
||||||
break
|
|
||||||
|
if let failure = onFailure {
|
||||||
|
failure()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshTags() {
|
func refreshTags(onSuccess: (() -> Void)? = nil, onFailure: (() -> Void)? = nil) {
|
||||||
self.isRefreshingTags = true
|
self.isRefreshingTags = true
|
||||||
|
|
||||||
let api = TagApi.getTags
|
let api = TagApi.getTags
|
||||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
|
|
||||||
self.checkNetworkResponse(response: response)
|
if self.checkNetworkResponse(response: response) {
|
||||||
|
|
||||||
switch response.response?.statusCode {
|
|
||||||
case 200, 201:
|
|
||||||
|
|
||||||
guard let data = response.data else {
|
guard let data = response.data else {
|
||||||
fatalError("error getting tags")
|
fatalError("error getting tags")
|
||||||
@ -104,6 +141,10 @@ class LiveUser: ObservableObject {
|
|||||||
|
|
||||||
self.isRefreshingTags = false
|
self.isRefreshingTags = false
|
||||||
|
|
||||||
|
if let success = onSuccess {
|
||||||
|
success()
|
||||||
|
}
|
||||||
|
|
||||||
let encoder = JSONEncoder()
|
let encoder = JSONEncoder()
|
||||||
do {
|
do {
|
||||||
UserDefaults.standard.set(String(data: try encoder.encode(tags), encoding: .utf8), forKey: "tags")
|
UserDefaults.standard.set(String(data: try encoder.encode(tags), encoding: .utf8), forKey: "tags")
|
||||||
@ -111,22 +152,34 @@ class LiveUser: ObservableObject {
|
|||||||
print("error encoding tags: \(error)")
|
print("error encoding tags: \(error)")
|
||||||
}
|
}
|
||||||
|
|
||||||
case _:
|
} else {
|
||||||
break
|
|
||||||
|
if let failure = onFailure {
|
||||||
|
failure()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkNetworkResponse(response: AFDataResponse<Any>) {
|
func checkNetworkResponse(response: AFDataResponse<Any>) -> Bool {
|
||||||
|
|
||||||
switch response.response?.statusCode {
|
if let statusCode = response.response?.statusCode {
|
||||||
case 401:
|
switch statusCode {
|
||||||
|
case 401: // token has expired
|
||||||
self.loggedIn = false
|
self.loggedIn = false
|
||||||
case _:
|
return false
|
||||||
print("error making request")
|
case 400..<500:
|
||||||
|
return false
|
||||||
|
case 500..<600:
|
||||||
|
return false
|
||||||
|
case _: // 200 -> Success
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func loadUserDefaults() -> LiveUser {
|
func loadUserDefaults() -> LiveUser {
|
||||||
let defaults = UserDefaults.standard
|
let defaults = UserDefaults.standard
|
||||||
let decoder = JSONDecoder()
|
let decoder = JSONDecoder()
|
||||||
|
@ -269,13 +269,13 @@ class Playlist: Identifiable, Equatable, Codable, ObservableObject {
|
|||||||
do{
|
do{
|
||||||
description_overwrite = try container.decode(String.self, forKey: .description_overwrite)
|
description_overwrite = try container.decode(String.self, forKey: .description_overwrite)
|
||||||
}catch {
|
}catch {
|
||||||
debugPrint("no description overwrite")
|
// debugPrint("no description overwrite")
|
||||||
}
|
}
|
||||||
|
|
||||||
do{
|
do{
|
||||||
description_suffix = try container.decode(String.self, forKey: .description_suffix)
|
description_suffix = try container.decode(String.self, forKey: .description_suffix)
|
||||||
}catch {
|
}catch {
|
||||||
debugPrint("no description suffix")
|
// debugPrint("no description suffix")
|
||||||
}
|
}
|
||||||
last_updated = try container.decode(String.self, forKey: .last_updated)
|
last_updated = try container.decode(String.self, forKey: .last_updated)
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@ class User: Identifiable, Decodable {
|
|||||||
var type: UserType
|
var type: UserType
|
||||||
|
|
||||||
var last_login: String
|
var last_login: String
|
||||||
|
var last_keygen: String
|
||||||
|
|
||||||
var spotify_linked: Bool
|
var spotify_linked: Bool
|
||||||
var lastfm_username: String?
|
var lastfm_username: String?
|
||||||
|
|
||||||
@ -33,6 +35,7 @@ class User: Identifiable, Decodable {
|
|||||||
type: UserType = .user,
|
type: UserType = .user,
|
||||||
|
|
||||||
last_login: String,
|
last_login: String,
|
||||||
|
last_keygen: String,
|
||||||
spotify_linked: Bool,
|
spotify_linked: Bool,
|
||||||
lastfm_username: String?){
|
lastfm_username: String?){
|
||||||
|
|
||||||
@ -41,6 +44,7 @@ class User: Identifiable, Decodable {
|
|||||||
self.type = type
|
self.type = type
|
||||||
|
|
||||||
self.last_login = last_login
|
self.last_login = last_login
|
||||||
|
self.last_keygen = last_keygen
|
||||||
self.spotify_linked = spotify_linked
|
self.spotify_linked = spotify_linked
|
||||||
self.lastfm_username = lastfm_username
|
self.lastfm_username = lastfm_username
|
||||||
}
|
}
|
||||||
|
@ -144,7 +144,6 @@ extension PlaylistApi: ApiRequest {
|
|||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
print(playlist)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ extension TagApi: ApiRequest {
|
|||||||
return "api/tag"
|
return "api/tag"
|
||||||
case .runTag(let tag_id):
|
case .runTag(let tag_id):
|
||||||
return "api/tag/\(tag_id)/update"
|
return "api/tag/\(tag_id)/update"
|
||||||
case .updateTag(let tag_id):
|
case .updateTag(let tag_id, _):
|
||||||
return "api/tag/\(tag_id)"
|
return "api/tag/\(tag_id)"
|
||||||
case .deleteTag(let tag_id):
|
case .deleteTag(let tag_id):
|
||||||
return "api/tag/\(tag_id)"
|
return "api/tag/\(tag_id)"
|
||||||
@ -64,7 +64,7 @@ extension TagApi: ApiRequest {
|
|||||||
return nil
|
return nil
|
||||||
case .runTag:
|
case .runTag:
|
||||||
return nil
|
return nil
|
||||||
case .updateTag(let _, let updates):
|
case .updateTag(_, let updates):
|
||||||
return updates
|
return updates
|
||||||
case .deleteTag:
|
case .deleteTag:
|
||||||
return nil
|
return nil
|
||||||
|
29
Mixonomer/Views/Admin/AdminList.swift
Normal file
29
Mixonomer/Views/Admin/AdminList.swift
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// AdminList.swift
|
||||||
|
// Mixonomer
|
||||||
|
//
|
||||||
|
// Created by Andy Pack on 11/08/2022.
|
||||||
|
// Copyright © 2022 Sarsoo. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct AdminList: View {
|
||||||
|
var body: some View {
|
||||||
|
NavigationView {
|
||||||
|
List{
|
||||||
|
Section {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listStyle(GroupedListStyle())
|
||||||
|
.navigationBarTitle(Text("Admin 🚨"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AdminList_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
AdminList()
|
||||||
|
}
|
||||||
|
}
|
@ -28,6 +28,8 @@ struct AppSkeleton: View {
|
|||||||
}
|
}
|
||||||
.tag(0)
|
.tag(0)
|
||||||
|
|
||||||
|
if let user = liveUser.user {
|
||||||
|
if let _ = user.lastfm_username {
|
||||||
TagList()
|
TagList()
|
||||||
.tabItem {
|
.tabItem {
|
||||||
VStack {
|
VStack {
|
||||||
@ -36,6 +38,21 @@ struct AppSkeleton: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.tag(1)
|
.tag(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let user = liveUser.user {
|
||||||
|
if user.type == .admin {
|
||||||
|
AdminList()
|
||||||
|
.tabItem( {
|
||||||
|
VStack {
|
||||||
|
Image(systemName: "person.badge.key.fill")
|
||||||
|
Text("Admin")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.tag(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsList()
|
SettingsList()
|
||||||
.tabItem {
|
.tabItem {
|
||||||
@ -44,7 +61,7 @@ struct AppSkeleton: View {
|
|||||||
Text("Settings")
|
Text("Settings")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.tag(2)
|
.tag(3)
|
||||||
|
|
||||||
}.onAppear {
|
}.onAppear {
|
||||||
self.fetchAll()
|
self.fetchAll()
|
||||||
@ -52,6 +69,7 @@ struct AppSkeleton: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func fetchAll() {
|
private func fetchAll() {
|
||||||
|
self.liveUser.refreshUser()
|
||||||
self.liveUser.refreshPlaylists()
|
self.liveUser.refreshPlaylists()
|
||||||
self.liveUser.refreshTags()
|
self.liveUser.refreshTags()
|
||||||
}
|
}
|
||||||
@ -60,5 +78,6 @@ struct AppSkeleton: View {
|
|||||||
struct RootView_Previews: PreviewProvider {
|
struct RootView_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
AppSkeleton()
|
AppSkeleton()
|
||||||
|
.environmentObject(LiveUser(playlists: [], tags: [], username: "user", loggedIn: false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,6 +184,11 @@ struct LoginScreen: View {
|
|||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.toast(isPresented: $showingToast, dismissAfter: 3.0){
|
||||||
|
ToastView(toastText)
|
||||||
|
.toastViewStyle(.failure)
|
||||||
|
}
|
||||||
|
.toastDimmedBackground(false)
|
||||||
.padding()
|
.padding()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,10 +94,7 @@ struct AddPlaylistSheet: View {
|
|||||||
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.liveUser.checkNetworkResponse(response: response)
|
if self.liveUser.checkNetworkResponse(response: response) {
|
||||||
|
|
||||||
switch response.response?.statusCode {
|
|
||||||
case 200, 201:
|
|
||||||
|
|
||||||
self.playlists.append(playlist)
|
self.playlists.append(playlist)
|
||||||
self.playlists = self.playlists.sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
|
self.playlists = self.playlists.sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
|
||||||
@ -105,8 +102,8 @@ struct AddPlaylistSheet: View {
|
|||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
self.presentationMode.wrappedValue.dismiss()
|
self.presentationMode.wrappedValue.dismiss()
|
||||||
|
|
||||||
case _:
|
} else {
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,5 +112,6 @@ struct AddPlaylistSheet: View {
|
|||||||
struct AddPlaylistSheet_Previews: PreviewProvider {
|
struct AddPlaylistSheet_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
AddPlaylistSheet(playlists: .constant([]), username: .constant("username"))
|
AddPlaylistSheet(playlists: .constant([]), username: .constant("username"))
|
||||||
|
.environmentObject(LiveUser(playlists: [], tags: [], username: "user", loggedIn: false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,17 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import ToastUI
|
||||||
|
|
||||||
struct PlaylistList: View {
|
struct PlaylistList: View {
|
||||||
|
|
||||||
@EnvironmentObject var liveUser: LiveUser
|
@EnvironmentObject var liveUser: LiveUser
|
||||||
@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 showingToast = false
|
||||||
|
@State private var toastText = ""
|
||||||
|
@State private var toastSuccess = true
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
List{
|
List{
|
||||||
@ -25,13 +30,11 @@ struct PlaylistList: View {
|
|||||||
indexSet.forEach { index in
|
indexSet.forEach { index in
|
||||||
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
|
||||||
self.liveUser.checkNetworkResponse(response: response)
|
|
||||||
|
|
||||||
switch response.response?.statusCode {
|
if self.liveUser.checkNetworkResponse(response: response) {
|
||||||
case 200, 201:
|
|
||||||
break
|
} else {
|
||||||
case _:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,7 +47,19 @@ struct PlaylistList: View {
|
|||||||
}
|
}
|
||||||
.refreshable
|
.refreshable
|
||||||
{
|
{
|
||||||
self.liveUser.refreshPlaylists()
|
self.liveUser.refreshPlaylists(onSuccess: {
|
||||||
|
|
||||||
|
toastText = "Refreshed!"
|
||||||
|
toastSuccess = true
|
||||||
|
showingToast = true
|
||||||
|
|
||||||
|
}, onFailure: {
|
||||||
|
|
||||||
|
toastText = "Refresh Failed"
|
||||||
|
toastSuccess = false
|
||||||
|
showingToast = true
|
||||||
|
|
||||||
|
})
|
||||||
}
|
}
|
||||||
.navigationBarTitle(Text("Playlists 📻"))
|
.navigationBarTitle(Text("Playlists 📻"))
|
||||||
.navigationBarItems(trailing:
|
.navigationBarItems(trailing:
|
||||||
@ -55,6 +70,18 @@ struct PlaylistList: View {
|
|||||||
AddPlaylistSheet(playlists: self.$liveUser.playlists, username: self.$liveUser.username)
|
AddPlaylistSheet(playlists: self.$liveUser.playlists, username: self.$liveUser.username)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
.toast(isPresented: $showingToast, dismissAfter: 3.0){
|
||||||
|
|
||||||
|
if toastSuccess {
|
||||||
|
ToastView(toastText)
|
||||||
|
.toastViewStyle(.success)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ToastView(toastText)
|
||||||
|
.toastViewStyle(.failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toastDimmedBackground(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,5 +89,6 @@ struct PlaylistList: View {
|
|||||||
struct PlaylistList_Previews: PreviewProvider {
|
struct PlaylistList_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
PlaylistList()
|
PlaylistList()
|
||||||
|
.environmentObject(LiveUser(playlists: [], tags: [], username: "user", loggedIn: false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,12 +31,10 @@ struct PlaylistRow: View {
|
|||||||
.validate()
|
.validate()
|
||||||
.responseJSON{ response in
|
.responseJSON{ response in
|
||||||
|
|
||||||
self.liveUser.checkNetworkResponse(response: response)
|
if self.liveUser.checkNetworkResponse(response: response) {
|
||||||
|
|
||||||
switch response.response?.statusCode {
|
}
|
||||||
case 200, 201:
|
else {
|
||||||
break
|
|
||||||
case _:
|
|
||||||
self.showingNetworkError = true
|
self.showingNetworkError = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -65,5 +63,6 @@ struct PlaylistRow_Previews: PreviewProvider {
|
|||||||
PlaylistView(playlist: .constant(
|
PlaylistView(playlist: .constant(
|
||||||
Playlist(name: "playlist name", username: "username")
|
Playlist(name: "playlist name", username: "username")
|
||||||
))
|
))
|
||||||
|
.environmentObject(LiveUser(playlists: [], tags: [], username: "user", loggedIn: false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import ToastUI
|
||||||
import SwiftyJSON
|
import SwiftyJSON
|
||||||
import SwiftUICharts
|
import SwiftUICharts
|
||||||
|
|
||||||
@ -17,7 +18,11 @@ struct PlaylistView: View {
|
|||||||
|
|
||||||
@State private var showingSheet = false
|
@State private var showingSheet = false
|
||||||
@State private var isRefreshing = false
|
@State private var isRefreshing = false
|
||||||
@State private var showingNetworkError = false
|
|
||||||
|
// TOAST
|
||||||
|
@State private var showingToast = false
|
||||||
|
@State private var toastText = ""
|
||||||
|
@State private var toastSuccess = true
|
||||||
|
|
||||||
var chartStyle: ChartStyle {
|
var chartStyle: ChartStyle {
|
||||||
get {
|
get {
|
||||||
@ -194,14 +199,18 @@ struct PlaylistView: View {
|
|||||||
Text("Open")
|
Text("Open")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.toast(isPresented: $showingToast, dismissAfter: 3.0){
|
||||||
|
|
||||||
|
if toastSuccess {
|
||||||
// alert seems to need to be within list root element
|
ToastView(toastText)
|
||||||
// else weird crash on half drag back
|
.toastViewStyle(.success)
|
||||||
.alert(isPresented: $showingNetworkError) {
|
|
||||||
Alert(title: Text("Network Error"),
|
|
||||||
message: Text("Could not refresh playlist"))
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
ToastView(toastText)
|
||||||
|
.toastViewStyle(.failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toastDimmedBackground(false)
|
||||||
|
|
||||||
}
|
}
|
||||||
.navigationBarTitle(Text(playlist.name))
|
.navigationBarTitle(Text(playlist.name))
|
||||||
@ -216,32 +225,39 @@ struct PlaylistView: View {
|
|||||||
.validate()
|
.validate()
|
||||||
.responseJSON{ response in
|
.responseJSON{ response in
|
||||||
|
|
||||||
self.liveUser.checkNetworkResponse(response: response)
|
if self.liveUser.checkNetworkResponse(response: response) {
|
||||||
|
|
||||||
switch response.response?.statusCode {
|
toastText = "Running!"
|
||||||
case 200, 201:
|
toastSuccess = true
|
||||||
break
|
showingToast = true
|
||||||
case _:
|
|
||||||
self.showingNetworkError = true
|
} else {
|
||||||
|
|
||||||
|
toastText = "Run Request Failed"
|
||||||
|
toastSuccess = false
|
||||||
|
showingToast = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//TODO: do better error checking
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshStats() {
|
func refreshStats() {
|
||||||
let api = PlaylistApi.refreshStats(name: playlist.name)
|
let api = PlaylistApi.refreshStats(name: playlist.name)
|
||||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
|
|
||||||
self.liveUser.checkNetworkResponse(response: response)
|
if self.liveUser.checkNetworkResponse(response: response) {
|
||||||
|
|
||||||
|
toastText = "Refreshing Stats!"
|
||||||
|
toastSuccess = true
|
||||||
|
showingToast = true
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
toastText = "Stat Refresh Failed"
|
||||||
|
toastSuccess = false
|
||||||
|
showingToast = true
|
||||||
|
|
||||||
switch response.response?.statusCode {
|
|
||||||
case 200, 201:
|
|
||||||
break
|
|
||||||
case _:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//TODO: do better error checking
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func openPlaylist() {
|
func openPlaylist() {
|
||||||
@ -254,12 +270,9 @@ struct PlaylistView: View {
|
|||||||
let api = PlaylistApi.updatePlaylist(name: playlist.name, updates: updates)
|
let api = PlaylistApi.updatePlaylist(name: playlist.name, updates: updates)
|
||||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
|
|
||||||
self.liveUser.checkNetworkResponse(response: response)
|
if self.liveUser.checkNetworkResponse(response: response) {
|
||||||
|
|
||||||
switch response.response?.statusCode {
|
|
||||||
case 200, 201:
|
|
||||||
debugPrint("success")
|
debugPrint("success")
|
||||||
case _:
|
} else {
|
||||||
debugPrint("error")
|
debugPrint("error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -270,10 +283,7 @@ struct PlaylistView: View {
|
|||||||
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
|
||||||
|
|
||||||
self.liveUser.checkNetworkResponse(response: response)
|
if self.liveUser.checkNetworkResponse(response: response) {
|
||||||
|
|
||||||
switch response.response?.statusCode {
|
|
||||||
case 200, 201:
|
|
||||||
|
|
||||||
guard let data = response.data else {
|
guard let data = response.data else {
|
||||||
fatalError("error getting playlist")
|
fatalError("error getting playlist")
|
||||||
@ -281,13 +291,20 @@ struct PlaylistView: View {
|
|||||||
|
|
||||||
self.playlist = PlaylistApi.fromJSON(playlist: data)!
|
self.playlist = PlaylistApi.fromJSON(playlist: data)!
|
||||||
|
|
||||||
case _:
|
toastText = "Refreshed!"
|
||||||
break
|
toastSuccess = true
|
||||||
|
showingToast = true
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
toastText = "Refresh Failed"
|
||||||
|
toastSuccess = false
|
||||||
|
showingToast = true
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.isRefreshing = false
|
self.isRefreshing = false
|
||||||
}
|
}
|
||||||
//TODO: do better error checking
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,6 +318,7 @@ struct PlaylistView_Previews: PreviewProvider {
|
|||||||
lastfm_stat_artist_percent: 80
|
lastfm_stat_artist_percent: 80
|
||||||
)
|
)
|
||||||
))
|
))
|
||||||
|
.environmentObject(LiveUser(playlists: [], tags: [], username: "user", loggedIn: false))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,10 +76,7 @@ struct AddTagSheet: View {
|
|||||||
let api = TagApi.newTag(tag_id: tag_id)
|
let api = TagApi.newTag(tag_id: tag_id)
|
||||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
|
|
||||||
self.liveUser.checkNetworkResponse(response: response)
|
if self.liveUser.checkNetworkResponse(response: response) {
|
||||||
|
|
||||||
switch response.response?.statusCode {
|
|
||||||
case 200, 201:
|
|
||||||
|
|
||||||
self.tags.append(tag)
|
self.tags.append(tag)
|
||||||
self.tags = self.tags.sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
|
self.tags = self.tags.sorted(by: { $0.name.lowercased() < $1.name.lowercased() })
|
||||||
@ -87,8 +84,8 @@ struct AddTagSheet: View {
|
|||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
self.presentationMode.wrappedValue.dismiss()
|
self.presentationMode.wrappedValue.dismiss()
|
||||||
|
|
||||||
case _:
|
} else {
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,5 +94,6 @@ struct AddTagSheet: View {
|
|||||||
struct AddTagSheet_Previews: PreviewProvider {
|
struct AddTagSheet_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
AddTagSheet(tags: .constant([]), username: .constant("username"))
|
AddTagSheet(tags: .constant([]), username: .constant("username"))
|
||||||
|
.environmentObject(LiveUser(playlists: [], tags: [], username: "user", loggedIn: false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,17 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import ToastUI
|
||||||
|
|
||||||
struct TagList: View {
|
struct TagList: View {
|
||||||
|
|
||||||
@EnvironmentObject var liveUser: LiveUser
|
@EnvironmentObject var liveUser: LiveUser
|
||||||
@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 showingToast = false
|
||||||
|
@State private var toastText = ""
|
||||||
|
@State private var toastSuccess = true
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
List{
|
List{
|
||||||
@ -26,13 +31,11 @@ struct TagList: View {
|
|||||||
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
|
||||||
|
|
||||||
self.liveUser.checkNetworkResponse(response: response)
|
if self.liveUser.checkNetworkResponse(response: response) {
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
switch response.response?.statusCode {
|
|
||||||
case 200, 201:
|
|
||||||
break
|
|
||||||
case _:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -44,7 +47,19 @@ struct TagList: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.refreshable {
|
.refreshable {
|
||||||
self.liveUser.refreshTags()
|
self.liveUser.refreshTags(onSuccess: {
|
||||||
|
|
||||||
|
toastText = "Refreshed!"
|
||||||
|
toastSuccess = true
|
||||||
|
showingToast = true
|
||||||
|
|
||||||
|
}, onFailure: {
|
||||||
|
|
||||||
|
toastText = "Refresh Failed"
|
||||||
|
toastSuccess = false
|
||||||
|
showingToast = true
|
||||||
|
|
||||||
|
})
|
||||||
}
|
}
|
||||||
.navigationBarTitle(Text("Tags 🎷"))
|
.navigationBarTitle(Text("Tags 🎷"))
|
||||||
.navigationBarItems(
|
.navigationBarItems(
|
||||||
@ -59,6 +74,18 @@ struct TagList: View {
|
|||||||
AddTagSheet(tags: self.$liveUser.tags, username: self.$liveUser.username)
|
AddTagSheet(tags: self.$liveUser.tags, username: self.$liveUser.username)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
.toast(isPresented: $showingToast, dismissAfter: 3.0){
|
||||||
|
|
||||||
|
if toastSuccess {
|
||||||
|
ToastView(toastText)
|
||||||
|
.toastViewStyle(.success)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ToastView(toastText)
|
||||||
|
.toastViewStyle(.failure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toastDimmedBackground(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,5 +93,6 @@ struct TagList: View {
|
|||||||
struct TagList_Previews: PreviewProvider {
|
struct TagList_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
TagList()
|
TagList()
|
||||||
|
.environmentObject(LiveUser(playlists: [], tags: [], username: "user", loggedIn: false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,12 +31,9 @@ struct TagRow: View {
|
|||||||
.validate()
|
.validate()
|
||||||
.responseJSON{ response in
|
.responseJSON{ response in
|
||||||
|
|
||||||
self.liveUser.checkNetworkResponse(response: response)
|
if self.liveUser.checkNetworkResponse(response: response) {
|
||||||
|
|
||||||
switch response.response?.statusCode {
|
} else {
|
||||||
case 200, 201:
|
|
||||||
break
|
|
||||||
case _:
|
|
||||||
self.showingNetworkError = true
|
self.showingNetworkError = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -69,5 +66,6 @@ struct TagRow_Previews: PreviewProvider {
|
|||||||
|
|
||||||
last_updated: "10th Feb")
|
last_updated: "10th Feb")
|
||||||
))
|
))
|
||||||
|
.environmentObject(LiveUser(playlists: [], tags: [], username: "user", loggedIn: false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,13 +120,10 @@ struct TagView: View {
|
|||||||
let api = TagApi.runTag(tag_id: tag.tag_id)
|
let api = TagApi.runTag(tag_id: tag.tag_id)
|
||||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
|
|
||||||
self.liveUser.checkNetworkResponse(response: response)
|
if self.liveUser.checkNetworkResponse(response: response) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
switch response.response?.statusCode {
|
|
||||||
case 200, 201:
|
|
||||||
break
|
|
||||||
case _:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//TODO: do better error checking
|
//TODO: do better error checking
|
||||||
@ -136,13 +133,10 @@ struct TagView: View {
|
|||||||
let api = TagApi.updateTag(tag_id: tag.tag_id, updates: updates)
|
let api = TagApi.updateTag(tag_id: tag.tag_id, updates: updates)
|
||||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
|
|
||||||
self.liveUser.checkNetworkResponse(response: response)
|
if self.liveUser.checkNetworkResponse(response: response) {
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
switch response.response?.statusCode {
|
|
||||||
case 200, 201:
|
|
||||||
break
|
|
||||||
case _:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//TODO: do better error checking
|
//TODO: do better error checking
|
||||||
@ -152,10 +146,7 @@ struct TagView: View {
|
|||||||
let api = TagApi.getTag(tag_id: self.tag.tag_id)
|
let api = TagApi.getTag(tag_id: self.tag.tag_id)
|
||||||
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
RequestBuilder.buildRequest(apiRequest: api).responseJSON{ response in
|
||||||
|
|
||||||
self.liveUser.checkNetworkResponse(response: response)
|
if self.liveUser.checkNetworkResponse(response: response) {
|
||||||
|
|
||||||
switch response.response?.statusCode {
|
|
||||||
case 200, 201:
|
|
||||||
|
|
||||||
guard let data = response.data else {
|
guard let data = response.data else {
|
||||||
fatalError("error getting tag")
|
fatalError("error getting tag")
|
||||||
@ -169,8 +160,8 @@ struct TagView: View {
|
|||||||
self.tag = tag
|
self.tag = tag
|
||||||
}
|
}
|
||||||
|
|
||||||
case _:
|
} else {
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.isRefreshing = false
|
self.isRefreshing = false
|
||||||
@ -196,5 +187,6 @@ struct TagView_Previews: PreviewProvider {
|
|||||||
|
|
||||||
last_updated: "10th Feb")
|
last_updated: "10th Feb")
|
||||||
))
|
))
|
||||||
|
.environmentObject(LiveUser(playlists: [], tags: [], username: "user", loggedIn: false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user