From d22868ed2d19a9a7de9bd304437d65e49a6b4af7 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Wed, 29 Sep 2021 12:14:43 +0200 Subject: [PATCH] Settings UI and code improvements --- Model/InstancesModel.swift | 21 +- Pearvidious.xcodeproj/project.pbxproj | 42 ++-- ...sView.swift => AccountsSettingsView.swift} | 22 +- Shared/Settings/DefaultAccountHint.swift | 38 +++ Shared/Settings/InstancesSettingsView.swift | 217 ++---------------- .../Settings/AccountSettingsView.swift | 4 +- macOS/Settings/InstancesSettingsView.swift | 127 ++++++++++ 7 files changed, 244 insertions(+), 227 deletions(-) rename Shared/Settings/{InstanceDetailsSettingsView.swift => AccountsSettingsView.swift} (84%) create mode 100644 Shared/Settings/DefaultAccountHint.swift rename {Shared => macOS}/Settings/AccountSettingsView.swift (94%) create mode 100644 macOS/Settings/InstancesSettingsView.swift diff --git a/Model/InstancesModel.swift b/Model/InstancesModel.swift index d1a7a6c8..328a87ba 100644 --- a/Model/InstancesModel.swift +++ b/Model/InstancesModel.swift @@ -5,10 +5,13 @@ final class InstancesModel: ObservableObject { @Published var defaultAccount: Instance.Account? init() { - if let id = Defaults[.defaultAccountID] { - let uuid = UUID(uuidString: id) - defaultAccount = Defaults[.accounts].first { $0.id == uuid } + guard let id = Defaults[.defaultAccountID], + let uuid = UUID(uuidString: id) + else { + return } + + defaultAccount = findAccount(uuid) } func find(_ id: Instance.ID?) -> Instance? { @@ -38,6 +41,10 @@ final class InstancesModel: ObservableObject { } } + func findAccount(_ id: Instance.Account.ID) -> Instance.Account? { + Defaults[.accounts].first { $0.id == id } + } + func addAccount(instance: Instance, name: String, sid: String) -> Instance.Account { let account = Instance.Account(instanceID: instance.id, name: name, url: instance.url, sid: sid) Defaults[.accounts].append(account) @@ -51,12 +58,12 @@ final class InstancesModel: ObservableObject { } } - func resetDefaultAccount() { - setDefaultAccount(nil) - } - func setDefaultAccount(_ account: Instance.Account?) { Defaults[.defaultAccountID] = account?.id.uuidString defaultAccount = account } + + func resetDefaultAccount() { + setDefaultAccount(nil) + } } diff --git a/Pearvidious.xcodeproj/project.pbxproj b/Pearvidious.xcodeproj/project.pbxproj index 206909d9..468823a6 100644 --- a/Pearvidious.xcodeproj/project.pbxproj +++ b/Pearvidious.xcodeproj/project.pbxproj @@ -54,20 +54,16 @@ 37484C1A26FC837400287258 /* PlaybackSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1826FC837400287258 /* PlaybackSettingsView.swift */; }; 37484C1B26FC837400287258 /* PlaybackSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1826FC837400287258 /* PlaybackSettingsView.swift */; }; 37484C1D26FC83A400287258 /* InstancesSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1C26FC83A400287258 /* InstancesSettingsView.swift */; }; - 37484C1E26FC83A400287258 /* InstancesSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1C26FC83A400287258 /* InstancesSettingsView.swift */; }; 37484C1F26FC83A400287258 /* InstancesSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1C26FC83A400287258 /* InstancesSettingsView.swift */; }; - 37484C2126FC83C400287258 /* AccountSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2026FC83C400287258 /* AccountSettingsView.swift */; }; 37484C2226FC83C400287258 /* AccountSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2026FC83C400287258 /* AccountSettingsView.swift */; }; - 37484C2326FC83C400287258 /* AccountSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2026FC83C400287258 /* AccountSettingsView.swift */; }; 37484C2526FC83E000287258 /* InstanceFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2426FC83E000287258 /* InstanceFormView.swift */; }; 37484C2626FC83E000287258 /* InstanceFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2426FC83E000287258 /* InstanceFormView.swift */; }; 37484C2726FC83E000287258 /* InstanceFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2426FC83E000287258 /* InstanceFormView.swift */; }; 37484C2926FC83FF00287258 /* AccountFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2826FC83FF00287258 /* AccountFormView.swift */; }; 37484C2A26FC83FF00287258 /* AccountFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2826FC83FF00287258 /* AccountFormView.swift */; }; 37484C2B26FC83FF00287258 /* AccountFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2826FC83FF00287258 /* AccountFormView.swift */; }; - 37484C2D26FC844700287258 /* InstanceDetailsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2C26FC844700287258 /* InstanceDetailsSettingsView.swift */; }; - 37484C2E26FC844700287258 /* InstanceDetailsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2C26FC844700287258 /* InstanceDetailsSettingsView.swift */; }; - 37484C2F26FC844700287258 /* InstanceDetailsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2C26FC844700287258 /* InstanceDetailsSettingsView.swift */; }; + 37484C2D26FC844700287258 /* AccountsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2C26FC844700287258 /* AccountsSettingsView.swift */; }; + 37484C2F26FC844700287258 /* AccountsSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2C26FC844700287258 /* AccountsSettingsView.swift */; }; 37484C3126FCB8F900287258 /* AccountValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C3026FCB8F900287258 /* AccountValidator.swift */; }; 37484C3226FCB8F900287258 /* AccountValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C3026FCB8F900287258 /* AccountValidator.swift */; }; 37484C3326FCB8F900287258 /* AccountValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C3026FCB8F900287258 /* AccountValidator.swift */; }; @@ -265,6 +261,10 @@ 37F64FE426FE70A60081B69E /* RedrawOnViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F64FE326FE70A60081B69E /* RedrawOnViewModifier.swift */; }; 37F64FE526FE70A60081B69E /* RedrawOnViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F64FE326FE70A60081B69E /* RedrawOnViewModifier.swift */; }; 37F64FE626FE70A60081B69E /* RedrawOnViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F64FE326FE70A60081B69E /* RedrawOnViewModifier.swift */; }; + 37FD43DC270470B70073EE42 /* InstancesSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43DB270470B70073EE42 /* InstancesSettingsView.swift */; }; + 37FD43DE2704717F0073EE42 /* DefaultAccountHint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43DD2704717F0073EE42 /* DefaultAccountHint.swift */; }; + 37FD43DF2704717F0073EE42 /* DefaultAccountHint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43DD2704717F0073EE42 /* DefaultAccountHint.swift */; }; + 37FD43E02704717F0073EE42 /* DefaultAccountHint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD43DD2704717F0073EE42 /* DefaultAccountHint.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -319,7 +319,7 @@ 37484C2026FC83C400287258 /* AccountSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSettingsView.swift; sourceTree = ""; }; 37484C2426FC83E000287258 /* InstanceFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceFormView.swift; sourceTree = ""; }; 37484C2826FC83FF00287258 /* AccountFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFormView.swift; sourceTree = ""; }; - 37484C2C26FC844700287258 /* InstanceDetailsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceDetailsSettingsView.swift; sourceTree = ""; }; + 37484C2C26FC844700287258 /* AccountsSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsView.swift; sourceTree = ""; }; 37484C3026FCB8F900287258 /* AccountValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountValidator.swift; sourceTree = ""; }; 375168D52700FAFF008F96A6 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = ""; }; 375DFB5726F9DA010013F468 /* InstancesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesModel.swift; sourceTree = ""; }; @@ -400,6 +400,8 @@ 37F49BA226CAA59B00304AC0 /* Playlist+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Playlist+Fixtures.swift"; sourceTree = ""; }; 37F4AE7126828F0900BD60EA /* VideosCellsVertical.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideosCellsVertical.swift; sourceTree = ""; }; 37F64FE326FE70A60081B69E /* RedrawOnViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedrawOnViewModifier.swift; sourceTree = ""; }; + 37FD43DB270470B70073EE42 /* InstancesSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesSettingsView.swift; sourceTree = ""; }; + 37FD43DD2704717F0073EE42 /* DefaultAccountHint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultAccountHint.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -560,8 +562,8 @@ isa = PBXGroup; children = ( 37484C2826FC83FF00287258 /* AccountFormView.swift */, - 37484C2026FC83C400287258 /* AccountSettingsView.swift */, - 37484C2C26FC844700287258 /* InstanceDetailsSettingsView.swift */, + 37484C2C26FC844700287258 /* AccountsSettingsView.swift */, + 37FD43DD2704717F0073EE42 /* DefaultAccountHint.swift */, 37484C2426FC83E000287258 /* InstanceFormView.swift */, 37484C1C26FC83A400287258 /* InstancesSettingsView.swift */, 37484C1826FC837400287258 /* PlaybackSettingsView.swift */, @@ -624,6 +626,7 @@ 37BE0BD826A214500092E2DB /* macOS */ = { isa = PBXGroup; children = ( + 37FD43E1270472060073EE42 /* Settings */, 37BE0BDB26A2367F0092E2DB /* Player.swift */, 37BE0BD926A214630092E2DB /* PlayerViewController.swift */, ); @@ -762,6 +765,15 @@ path = Model; sourceTree = ""; }; + 37FD43E1270472060073EE42 /* Settings */ = { + isa = PBXGroup; + children = ( + 37484C2026FC83C400287258 /* AccountSettingsView.swift */, + 37FD43DB270470B70073EE42 /* InstancesSettingsView.swift */, + ); + path = Settings; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -1069,7 +1081,6 @@ 37F64FE426FE70A60081B69E /* RedrawOnViewModifier.swift in Sources */, 37B81AF926D2C9A700675966 /* VideoPlayerSizeModifier.swift in Sources */, 37BE0BD326A1D4780092E2DB /* Player.swift in Sources */, - 37484C2126FC83C400287258 /* AccountSettingsView.swift in Sources */, 37A9965E26D6F9B9006E3224 /* WatchNowView.swift in Sources */, 37CEE4C12677B697005A1EFE /* Stream.swift in Sources */, 37F4AE7226828F0900BD60EA /* VideosCellsVertical.swift in Sources */, @@ -1077,6 +1088,7 @@ 3748186626A7627F0084E870 /* Video+Fixtures.swift in Sources */, 37BA794726DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */, 377FC7DC267A081800A6BBAF /* PopularView.swift in Sources */, + 37FD43DE2704717F0073EE42 /* DefaultAccountHint.swift in Sources */, 3705B182267B4E4900704544 /* TrendingCategory.swift in Sources */, 37EAD86F267B9ED100D9E01B /* Segment.swift in Sources */, 375168D62700FAFF008F96A6 /* Debounce.swift in Sources */, @@ -1096,7 +1108,7 @@ 37B81AFF26D2CA3700675966 /* VideoDetails.swift in Sources */, 377FC7E5267A084E00A6BBAF /* SearchView.swift in Sources */, 376578912685490700D4EA09 /* PlaylistsView.swift in Sources */, - 37484C2D26FC844700287258 /* InstanceDetailsSettingsView.swift in Sources */, + 37484C2D26FC844700287258 /* AccountsSettingsView.swift in Sources */, 377A20A92693C9A2002842B8 /* TypedContentAccessors.swift in Sources */, 37484C3126FCB8F900287258 /* AccountValidator.swift in Sources */, 376B2E0726F920D600B1D64D /* SignInRequiredView.swift in Sources */, @@ -1145,13 +1157,13 @@ 37EAD86C267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */, 37CEE4C22677B697005A1EFE /* Stream.swift in Sources */, 371F2F1B269B43D300E4A7AB /* NavigationModel.swift in Sources */, + 37FD43DF2704717F0073EE42 /* DefaultAccountHint.swift in Sources */, 3761ABFE26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */, 37BA795026DC3E0E002A0235 /* Int+Format.swift in Sources */, - 37484C1E26FC83A400287258 /* InstancesSettingsView.swift in Sources */, 377FC7DD267A081A00A6BBAF /* PopularView.swift in Sources */, - 37484C2E26FC844700287258 /* InstanceDetailsSettingsView.swift in Sources */, 375DFB5926F9DA010013F468 /* InstancesModel.swift in Sources */, 3705B183267B4E4900704544 /* TrendingCategory.swift in Sources */, + 37FD43DC270470B70073EE42 /* InstancesSettingsView.swift in Sources */, 376B2E0826F920D600B1D64D /* SignInRequiredView.swift in Sources */, 37B81B0026D2CA3700675966 /* VideoDetails.swift in Sources */, 37484C1A26FC837400287258 /* PlaybackSettingsView.swift in Sources */, @@ -1240,6 +1252,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 37FD43E02704717F0073EE42 /* DefaultAccountHint.swift in Sources */, 37AAF28026737550007FC770 /* SearchView.swift in Sources */, 3788AC2D26F6842D00F6BAA9 /* WatchNowSectionBody.swift in Sources */, 37EAD871267B9ED100D9E01B /* Segment.swift in Sources */, @@ -1289,7 +1302,6 @@ 37BE0BD726A1D4A90092E2DB /* PlayerViewController.swift in Sources */, 37484C3326FCB8F900287258 /* AccountValidator.swift in Sources */, 37CEE4C32677B697005A1EFE /* Stream.swift in Sources */, - 37484C2326FC83C400287258 /* AccountSettingsView.swift in Sources */, 37C194C926F6A9C8005D3B96 /* RecentsModel.swift in Sources */, 37F64FE626FE70A60081B69E /* RedrawOnViewModifier.swift in Sources */, 37484C2B26FC83FF00287258 /* AccountFormView.swift in Sources */, @@ -1311,7 +1323,7 @@ 372915E82687E3B900F5A35B /* Defaults.swift in Sources */, 37BAB54C269B39FD00E75ED1 /* TVNavigationView.swift in Sources */, 3797758D2689345500DD52A8 /* Store.swift in Sources */, - 37484C2F26FC844700287258 /* InstanceDetailsSettingsView.swift in Sources */, + 37484C2F26FC844700287258 /* AccountsSettingsView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Shared/Settings/InstanceDetailsSettingsView.swift b/Shared/Settings/AccountsSettingsView.swift similarity index 84% rename from Shared/Settings/InstanceDetailsSettingsView.swift rename to Shared/Settings/AccountsSettingsView.swift index 4db66716..02719b72 100644 --- a/Shared/Settings/InstanceDetailsSettingsView.swift +++ b/Shared/Settings/AccountsSettingsView.swift @@ -1,6 +1,6 @@ import SwiftUI -struct InstanceDetailsSettingsView: View { +struct AccountsSettingsView: View { let instanceID: Instance.ID? @State private var accountsChanged = false @@ -14,9 +14,9 @@ struct InstanceDetailsSettingsView: View { var body: some View { List { - Section(header: Text("Accounts")) { + Section(header: Text("Accounts"), footer: sectionFooter) { ForEach(instances.accounts(instanceID), id: \.self) { account in - #if !os(tvOS) + #if os(iOS) HStack(spacing: 2) { Text(account.description) if instances.defaultAccount == account { @@ -58,16 +58,24 @@ struct InstanceDetailsSettingsView: View { } } } + .navigationTitle(instance.shortDescription) + .sheet(isPresented: $presentingAccountForm, onDismiss: { accountsChanged.toggle() }) { + AccountFormView(instance: instance) + } #if os(iOS) .listStyle(.insetGrouped) #elseif os(tvOS) .frame(maxWidth: 1000) #endif + } - .navigationTitle(instance.shortDescription) - .sheet(isPresented: $presentingAccountForm, onDismiss: { accountsChanged.toggle() }) { - AccountFormView(instance: instance) - } + private var sectionFooter: some View { + #if os(iOS) + Text("Swipe right to toggle default account, swipe left to remove") + #else + Text("Tap to toggle default account, tap and hold to remove") + .foregroundColor(.secondary) + #endif } private func makeDefault(_ account: Instance.Account) { diff --git a/Shared/Settings/DefaultAccountHint.swift b/Shared/Settings/DefaultAccountHint.swift new file mode 100644 index 00000000..1504f6d1 --- /dev/null +++ b/Shared/Settings/DefaultAccountHint.swift @@ -0,0 +1,38 @@ +import Foundation +import SwiftUI + +struct DefaultAccountHint: View { + @EnvironmentObject private var instancesModel + + var body: some View { + Group { + if !instancesModel.defaultAccount.isNil { + VStack { + HStack(spacing: 2) { + hintText + .truncationMode(.middle) + .lineLimit(1) + } + } + } else { + Text("You have no default account set") + } + } + #if os(tvOS) + .foregroundColor(.gray) + #elseif os(macOS) + .font(.caption2) + .foregroundColor(.secondary) + #endif + } + + var hintText: some View { + Group { + if let account = instancesModel.defaultAccount { + Text( + "**\(account.description)** account on instance **\(account.instance.shortDescription)** is your default." + ) + } + } + } +} diff --git a/Shared/Settings/InstancesSettingsView.swift b/Shared/Settings/InstancesSettingsView.swift index d2f0600e..999a02fe 100644 --- a/Shared/Settings/InstancesSettingsView.swift +++ b/Shared/Settings/InstancesSettingsView.swift @@ -3,235 +3,62 @@ import SwiftUI struct InstancesSettingsView: View { @Default(.instances) private var instances - @EnvironmentObject private var instancesModel @EnvironmentObject private var api + @EnvironmentObject private var instancesModel @EnvironmentObject private var subscriptions @EnvironmentObject private var playlists @State private var selectedInstanceID: Instance.ID? @State private var selectedAccount: Instance.Account? - @State private var presentingAccountForm = false @State private var presentingInstanceForm = false @State private var savedFormInstanceID: Instance.ID? - @State private var presentingConfirmationDialog = false - @State private var presentingInstanceDetails = false - - var selectedInstance: Instance! { - instancesModel.find(selectedInstanceID) - } - - var accounts: [Instance.Account] { - guard selectedInstance != nil else { - return [] - } - - return instancesModel.accounts(selectedInstanceID) - } - var body: some View { Group { - #if os(iOS) - Section(header: instancesHeader, footer: defaultAccountSection) { - ForEach(instances) { instance in - Button(action: { - self.selectedInstanceID = instance.id - self.presentingInstanceDetails = true - }) { - HStack { - Text(instance.description) - Spacer() - NavigationLink( - isActive: .constant(false), - destination: { EmptyView() }, - label: { EmptyView() } - ) - .frame(maxWidth: 100) - } - } + Section(header: Text("Instances"), footer: DefaultAccountHint()) { + ForEach(instances) { instance in + NavigationLink(instance.description) { + AccountsSettingsView(instanceID: instance.id) + } + #if os(iOS) .swipeActions(edge: .trailing, allowsFullSwipe: false) { - Button("Remove", role: .destructive) { - instancesModel.remove(instance) - } + removeInstanceButton(instance) } .buttonStyle(.plain) - } - - addInstanceButton - } - .listStyle(.insetGrouped) - #elseif os(tvOS) - Section(header: instancesHeader) { - ForEach(instances) { instance in - Button(action: { - self.selectedInstanceID = instance.id - self.presentingInstanceDetails = true - }) { - Text(instance.description) - } + #else .contextMenu { - Button("Remove", role: .destructive) { - instancesModel.remove(instance) - } + removeInstanceButton(instance) } - } - - addInstanceButton - - defaultAccountSection - } - .frame(maxWidth: 1000, alignment: .leading) - .sheet(isPresented: $presentingAccountForm) { - AccountFormView(instance: selectedInstance, selectedAccount: $selectedAccount) - } - #else - Section { - Text("Instance") - - if !instances.isEmpty { - Picker("Instance", selection: $selectedInstanceID) { - ForEach(instances) { instance in - Text(instance.description).tag(Optional(instance.id)) - } - } - .labelsHidden() - } else { - Text("You have no instances configured") - .font(.caption) - .foregroundColor(.secondary) - } - - if !selectedInstance.isNil { - if accounts.isEmpty { - Text("You have no accounts for this instance") - .font(.caption) - .foregroundColor(.secondary) - } else { - Text("Accounts") - List(selection: $selectedAccount) { - ForEach(accounts) { account in - AccountSettingsView(account: account, - selectedAccount: $selectedAccount) - .tag(account) - } - } - #if os(macOS) - .listStyle(.inset(alternatesRowBackgrounds: true)) - #endif - } - } - - if selectedInstance != nil { - HStack { - Button("Add Account...") { - selectedAccount = nil - presentingAccountForm = true - } - Spacer() - - Button("Remove Instance", role: .destructive) { - presentingConfirmationDialog = true - } - .confirmationDialog( - "Are you sure you want to remove \(selectedInstance!.description) instance?", - isPresented: $presentingConfirmationDialog - ) { - Button("Remove Instance", role: .destructive) { - instancesModel.remove(selectedInstance!) - selectedInstanceID = instances.last?.id - } - } - - #if os(macOS) - .foregroundColor(.red) - #endif - } - } - - Button("Add Instance...") { - presentingInstanceForm = true - } - - defaultAccountSection - .padding(.top, 10) - } - .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) - - .onAppear { - selectedInstanceID = instances.first?.id - } - .sheet(isPresented: $presentingAccountForm) { - AccountFormView(instance: selectedInstance, selectedAccount: $selectedAccount) + #endif } - Spacer() - + addInstanceButton + } + #if os(iOS) + .listStyle(.insetGrouped) #endif } - .sheet(isPresented: $presentingInstanceForm, onDismiss: setSelectedInstanceToFormInstance) { + .sheet(isPresented: $presentingInstanceForm) { InstanceFormView(savedInstanceID: $savedFormInstanceID) } } - var instancesHeader: some View { - Text("Instances").background(instanceDetailsNavigationLink) - } - - var defaultAccountSection: some View { - Group { - if let account = instancesModel.defaultAccount { - VStack { - HStack(spacing: 2) { - Text("**\(account.description)** account on instance **\(account.instance.shortDescription)** is your default.") - .truncationMode(.middle) - .lineLimit(1) - - #if !os(tvOS) - - Button("Reset", action: resetDefaultAccount) - .buttonStyle(.plain) - .foregroundColor(.red) - #endif - } - } - } else { - Text("You have no default account set") - } - } - #if os(tvOS) - .foregroundColor(.gray) - #elseif os(macOS) - .font(.caption2) - .foregroundColor(.secondary) - #endif - } - - var instanceDetailsNavigationLink: some View { - NavigationLink( - isActive: $presentingInstanceDetails, - destination: { InstanceDetailsSettingsView(instanceID: selectedInstanceID) }, - label: { EmptyView() } - ) - .opacity(0) - } - private var addInstanceButton: some View { Button("Add Instance...") { presentingInstanceForm = true } } - private func resetDefaultAccount() { - instancesModel.resetDefaultAccount() + private func removeInstanceButton(_ instance: Instance) -> some View { + Button("Remove", role: .destructive) { + instancesModel.remove(instance) + } } - private func setSelectedInstanceToFormInstance() { - if let id = savedFormInstanceID { - selectedInstanceID = id - savedFormInstanceID = nil - } + private func resetDefaultAccount() { + instancesModel.resetDefaultAccount() } } diff --git a/Shared/Settings/AccountSettingsView.swift b/macOS/Settings/AccountSettingsView.swift similarity index 94% rename from Shared/Settings/AccountSettingsView.swift rename to macOS/Settings/AccountSettingsView.swift index e8a5f3e1..dbf7dece 100644 --- a/Shared/Settings/AccountSettingsView.swift +++ b/macOS/Settings/AccountSettingsView.swift @@ -38,9 +38,7 @@ struct AccountSettingsView: View { instances.removeAccount(account) } } - #if os(macOS) - .foregroundColor(.red) - #endif + .foregroundColor(.red) } .opacity(account == selectedAccount ? 1 : 0) } diff --git a/macOS/Settings/InstancesSettingsView.swift b/macOS/Settings/InstancesSettingsView.swift new file mode 100644 index 00000000..691e9f2b --- /dev/null +++ b/macOS/Settings/InstancesSettingsView.swift @@ -0,0 +1,127 @@ +import Defaults +import SwiftUI + +struct InstancesSettingsView: View { + @Default(.instances) private var instances + @EnvironmentObject private var model + + @EnvironmentObject private var api + @EnvironmentObject private var subscriptions + @EnvironmentObject private var playlists + + @State private var selectedInstanceID: Instance.ID? + @State private var selectedAccount: Instance.Account? + + @State private var presentingAccountForm = false + @State private var presentingInstanceForm = false + @State private var savedFormInstanceID: Instance.ID? + + @State private var presentingConfirmationDialog = false + + var body: some View { + Section { + Text("Instance") + + if !instances.isEmpty { + Picker("Instance", selection: $selectedInstanceID) { + ForEach(instances) { instance in + Text(instance.description).tag(Optional(instance.id)) + } + } + .labelsHidden() + } else { + Text("You have no instances configured") + .font(.caption) + .foregroundColor(.secondary) + } + + if !selectedInstance.isNil { + Text("Accounts") + List(selection: $selectedAccount) { + if accounts.isEmpty { + Text("You have no accounts for this instance") + .foregroundColor(.secondary) + } + ForEach(accounts) { account in + AccountSettingsView(account: account, selectedAccount: $selectedAccount) + .tag(account) + } + } + .listStyle(.inset(alternatesRowBackgrounds: true)) + } + + if selectedInstance != nil { + HStack { + Button("Add Account...") { + selectedAccount = nil + presentingAccountForm = true + } + + Spacer() + + Button("Remove Instance", role: .destructive) { + presentingConfirmationDialog = true + } + .confirmationDialog( + "Are you sure you want to remove \(selectedInstance!.description) instance?", + isPresented: $presentingConfirmationDialog + ) { + Button("Remove Instance", role: .destructive) { + model.remove(selectedInstance!) + selectedInstanceID = instances.last?.id + } + } + + .foregroundColor(.red) + } + } + + Button("Add Instance...") { + presentingInstanceForm = true + } + + DefaultAccountHint() + .padding(.top, 10) + } + .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) + + .onAppear { + selectedInstanceID = instances.first?.id + } + .sheet(isPresented: $presentingAccountForm) { + AccountFormView(instance: selectedInstance, selectedAccount: $selectedAccount) + } + .sheet(isPresented: $presentingInstanceForm, onDismiss: setSelectedInstanceToFormInstance) { + InstanceFormView(savedInstanceID: $savedFormInstanceID) + } + } + + private func setSelectedInstanceToFormInstance() { + if let id = savedFormInstanceID { + selectedInstanceID = id + savedFormInstanceID = nil + } + } + + var selectedInstance: Instance! { + model.find(selectedInstanceID) + } + + private var accounts: [Instance.Account] { + guard selectedInstance != nil else { + return [] + } + + return model.accounts(selectedInstanceID) + } +} + +struct InstancesSettingsView_Previews: PreviewProvider { + static var previews: some View { + VStack { + InstancesSettingsView() + } + .frame(width: 400, height: 270) + .environmentObject(InstancesModel()) + } +}