diff --git a/Model/Instance.swift b/Model/Instance.swift index 7b641245..208a5329 100644 --- a/Model/Instance.swift +++ b/Model/Instance.swift @@ -19,6 +19,10 @@ struct Instance: Defaults.Serializable, Hashable, Identifiable { self.sid = sid } + var instance: Instance { + Defaults[.instances].first { $0.id == instanceID }! + } + var anonymizedSID: String { guard sid.count > 3 else { return "" @@ -35,42 +39,42 @@ struct Instance: Defaults.Serializable, Hashable, Identifiable { func hash(into hasher: inout Hasher) { hasher.combine(sid) } - } - struct AccountsBridge: Defaults.Bridge { - typealias Value = Account - typealias Serializable = [String: String] + struct AccountsBridge: Defaults.Bridge { + typealias Value = Account + typealias Serializable = [String: String] - func serialize(_ value: Value?) -> Serializable? { - guard let value = value else { - return nil + func serialize(_ value: Value?) -> Serializable? { + guard let value = value else { + return nil + } + + return [ + "id": value.id.uuidString, + "instanceID": value.instanceID.uuidString, + "name": value.name ?? "", + "url": value.url, + "sid": value.sid + ] } - return [ - "id": value.id.uuidString, - "instanceID": value.instanceID.uuidString, - "name": value.name ?? "", - "url": value.url, - "sid": value.sid - ] - } + func deserialize(_ object: Serializable?) -> Value? { + guard + let object = object, + let id = object["id"], + let instanceID = object["instanceID"], + let url = object["url"], + let sid = object["sid"] + else { + return nil + } - func deserialize(_ object: Serializable?) -> Value? { - guard - let object = object, - let id = object["id"], - let instanceID = object["instanceID"], - let url = object["url"], - let sid = object["sid"] - else { - return nil + let uuid = UUID(uuidString: id) + let instanceUUID = UUID(uuidString: instanceID)! + let name = object["name"] ?? "" + + return Account(id: uuid, instanceID: instanceUUID, name: name, url: url, sid: sid) } - - let uuid = UUID(uuidString: id) - let instanceUUID = UUID(uuidString: instanceID)! - let name = object["name"] ?? "" - - return Account(id: uuid, instanceID: instanceUUID, name: name, url: url, sid: sid) } } diff --git a/Model/InstancesModel.swift b/Model/InstancesModel.swift index 6045a790..d1a7a6c8 100644 --- a/Model/InstancesModel.swift +++ b/Model/InstancesModel.swift @@ -2,8 +2,13 @@ import Defaults import Foundation final class InstancesModel: ObservableObject { - var defaultAccount: Instance.Account! { - Defaults[.accounts].first + @Published var defaultAccount: Instance.Account? + + init() { + if let id = Defaults[.defaultAccountID] { + let uuid = UUID(uuidString: id) + defaultAccount = Defaults[.accounts].first { $0.id == uuid } + } } func find(_ id: Instance.ID?) -> Instance? { @@ -26,8 +31,10 @@ final class InstancesModel: ObservableObject { } func remove(_ instance: Instance) { + let accounts = accounts(instance.id) if let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) { Defaults[.instances].remove(at: index) + accounts.forEach { removeAccount($0) } } } @@ -43,4 +50,13 @@ final class InstancesModel: ObservableObject { Defaults[.accounts].remove(at: accountIndex) } } + + func resetDefaultAccount() { + setDefaultAccount(nil) + } + + func setDefaultAccount(_ account: Instance.Account?) { + Defaults[.defaultAccountID] = account?.id.uuidString + defaultAccount = account + } } diff --git a/Shared/Defaults.swift b/Shared/Defaults.swift index 4cecdd04..f81e7fca 100644 --- a/Shared/Defaults.swift +++ b/Shared/Defaults.swift @@ -7,6 +7,7 @@ extension Defaults.Keys { static let instances = Key<[Instance]>("instances", default: []) static let accounts = Key<[Instance.Account]>("accounts", default: []) + static let defaultAccountID = Key("defaultAccountID") static let selectedPlaylistID = Key("selectedPlaylistID") static let showingAddToPlaylist = Key("showingAddToPlaylist", default: false) diff --git a/Shared/PearvidiousApp.swift b/Shared/PearvidiousApp.swift index 4a565a41..203313d0 100644 --- a/Shared/PearvidiousApp.swift +++ b/Shared/PearvidiousApp.swift @@ -40,10 +40,8 @@ struct PearvidiousApp: App { search.api = api subscriptions.api = api - guard api.account.isNil, instances.defaultAccount != nil else { - return + if let account = instances.defaultAccount { + api.setAccount(account) } - - api.setAccount(instances.defaultAccount) } } diff --git a/Shared/Settings/AccountSettingsView.swift b/Shared/Settings/AccountSettingsView.swift index adfaab4c..e8a5f3e1 100644 --- a/Shared/Settings/AccountSettingsView.swift +++ b/Shared/Settings/AccountSettingsView.swift @@ -1,7 +1,7 @@ +import Defaults import SwiftUI struct AccountSettingsView: View { - let instance: Instance let account: Instance.Account @Binding var selectedAccount: Instance.Account? @@ -11,11 +11,22 @@ struct AccountSettingsView: View { var body: some View { HStack { - Text(account.description) + HStack(spacing: 2) { + Text(account.description) + if instances.defaultAccount == account { + Text("— default") + .foregroundColor(.secondary) + } + } Spacer() HStack { + if instances.defaultAccount != account { + Button("Make default", action: makeDefault) + } else { + Button("Reset default", action: resetDefault) + } Button("Remove", role: .destructive) { presentingRemovalConfirmationDialog = true } @@ -34,4 +45,12 @@ struct AccountSettingsView: View { .opacity(account == selectedAccount ? 1 : 0) } } + + private func makeDefault() { + instances.setDefaultAccount(account) + } + + private func resetDefault() { + instances.resetDefaultAccount() + } } diff --git a/Shared/Settings/InstanceDetailsSettingsView.swift b/Shared/Settings/InstanceDetailsSettingsView.swift index 9dc9e06a..00d67b89 100644 --- a/Shared/Settings/InstanceDetailsSettingsView.swift +++ b/Shared/Settings/InstanceDetailsSettingsView.swift @@ -16,14 +16,24 @@ struct InstanceDetailsSettingsView: View { List { Section(header: Text("Accounts")) { ForEach(instances.accounts(instanceID)) { account in - Text(account.description) + HStack(spacing: 2) { + Text(account.description) + if instances.defaultAccount == account { + Text("— default") + .foregroundColor(.secondary) + } + } #if !os(tvOS) - .swipeActions(edge: .trailing, allowsFullSwipe: false) { - Button("Remove", role: .destructive) { - instances.removeAccount(account) - accountsChanged.toggle() + .swipeActions(edge: .leading, allowsFullSwipe: true) { + if instances.defaultAccount != account { + Button("Make Default", action: { makeDefault(account) }) + } else { + Button("Reset Default", action: resetDefaultAccount) } } + .swipeActions(edge: .trailing, allowsFullSwipe: false) { + Button("Remove", role: .destructive, action: { removeAccount(account) }) + } #endif } .redrawOn(change: accountsChanged) @@ -42,4 +52,19 @@ struct InstanceDetailsSettingsView: View { AccountFormView(instance: instance) } } + + private func makeDefault(_ account: Instance.Account) { + instances.setDefaultAccount(account) + accountsChanged.toggle() + } + + private func resetDefaultAccount() { + instances.resetDefaultAccount() + accountsChanged.toggle() + } + + private func removeAccount(_ account: Instance.Account) { + instances.removeAccount(account) + accountsChanged.toggle() + } } diff --git a/Shared/Settings/InstancesSettingsView.swift b/Shared/Settings/InstancesSettingsView.swift index b7826bb3..04645094 100644 --- a/Shared/Settings/InstancesSettingsView.swift +++ b/Shared/Settings/InstancesSettingsView.swift @@ -34,7 +34,7 @@ struct InstancesSettingsView: View { var body: some View { Group { #if os(iOS) - Section(header: instancesHeader) { + Section(header: instancesHeader, footer: instancesFooter) { ForEach(instances) { instance in Button(action: { self.selectedInstanceID = instance.id @@ -81,7 +81,7 @@ struct InstancesSettingsView: View { .foregroundColor(.secondary) } - if let instance = selectedInstance { + if !selectedInstance.isNil { if accounts.isEmpty { Text("You have no accounts for this instance") .font(.caption) @@ -90,7 +90,7 @@ struct InstancesSettingsView: View { Text("Accounts") List(selection: $selectedAccount) { ForEach(accounts) { account in - AccountSettingsView(instance: instance, account: account, + AccountSettingsView(account: account, selectedAccount: $selectedAccount) .tag(account) } @@ -131,6 +131,9 @@ struct InstancesSettingsView: View { Button("Add Instance...") { presentingInstanceForm = true } + + defaultAccountSection + .padding(.top, 10) } .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) @@ -154,6 +157,24 @@ struct InstancesSettingsView: View { Text("Instances").background(instanceDetailsNavigationLink) } + var instancesFooter: some View { + Group { + if let account = instancesModel.defaultAccount { + HStack(spacing: 2) { + Text("**\(account.description)** account on instance **\(account.instance.shortDescription)** is your default.") + .truncationMode(.middle) + .lineLimit(1) + + Button("Reset", action: resetDefaultAccount) + .buttonStyle(.plain) + .foregroundColor(.red) + } + } else { + Text("You have no default account set") + } + } + } + var instanceDetailsNavigationLink: some View { NavigationLink( isActive: $presentingInstanceDetails, @@ -162,7 +183,30 @@ struct InstancesSettingsView: View { ) } - func setSelectedInstanceToFormInstance() { + private var defaultAccountSection: some View { + Group { + if let account = instancesModel.defaultAccount { + HStack(spacing: 2) { + Text("**\(account.description)** account on instance **\(account.instance.shortDescription)** is your default.") + .truncationMode(.middle) + .lineLimit(1) + Button("Reset", action: resetDefaultAccount) + .buttonStyle(.plain) + .foregroundColor(.red) + } + } else { + Text("You have no default account set") + } + } + .font(.caption2) + .foregroundColor(.secondary) + } + + private func resetDefaultAccount() { + instancesModel.resetDefaultAccount() + } + + private func setSelectedInstanceToFormInstance() { if let id = savedFormInstanceID { selectedInstanceID = id savedFormInstanceID = nil @@ -172,6 +216,10 @@ struct InstancesSettingsView: View { struct InstancesSettingsView_Previews: PreviewProvider { static var previews: some View { - InstancesSettingsView() + VStack { + InstancesSettingsView() + } + .frame(width: 400, height: 270) + .environmentObject(InstancesModel()) } } diff --git a/Shared/Settings/SettingsView.swift b/Shared/Settings/SettingsView.swift index 729ddfa4..b69cdfc4 100644 --- a/Shared/Settings/SettingsView.swift +++ b/Shared/Settings/SettingsView.swift @@ -29,28 +29,27 @@ struct SettingsView: View { .tag(Tabs.playback) } .padding(20) - .frame(width: 400, height: 270) + .frame(width: 400, height: 310) #else NavigationView { List { InstancesSettingsView() PlaybackSettingsView() } + .navigationTitle("Settings") + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button("Done") { + dismiss() + } + #if !os(tvOS) + .keyboardShortcut(.cancelAction) + #endif + } + } #if os(iOS) .listStyle(.insetGrouped) #endif - - .navigationTitle("Settings") - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button("Done") { - dismiss() - } - #if !os(tvOS) - .keyboardShortcut(.cancelAction) - #endif - } - } } #endif }