diff options
| author | Christian Cleberg <[email protected]> | 2026-04-01 11:36:01 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-01 11:37:35 -0500 |
| commit | 7b59ee6098c4649a51cafe4b58e3a675567e2c08 (patch) | |
| tree | 3ec7aa3c843c13d24a164d89f1d724ac48d2210a /Hutch/Views | |
| parent | 3f03c433a7435e283244e94f09bd513bd45f3202 (diff) | |
| download | hutch-2.6.0.tar.gz hutch-2.6.0.tar.bz2 hutch-2.6.0.zip | |
feat: add multi-account supportv2.6.0
Implements: https://todo.sr.ht/~ccleberg/Hutch/11
Diffstat (limited to 'Hutch/Views')
| -rw-r--r-- | Hutch/Views/More/AccountSwitcherView.swift | 92 | ||||
| -rw-r--r-- | Hutch/Views/More/AddAccountView.swift | 59 | ||||
| -rw-r--r-- | Hutch/Views/More/MoreView.swift | 16 |
3 files changed, 167 insertions, 0 deletions
diff --git a/Hutch/Views/More/AccountSwitcherView.swift b/Hutch/Views/More/AccountSwitcherView.swift new file mode 100644 index 0000000..6fe3c28 --- /dev/null +++ b/Hutch/Views/More/AccountSwitcherView.swift @@ -0,0 +1,92 @@ +import SwiftUI + +struct AccountSwitcherView: View { + @Environment(AppState.self) private var appState + @Environment(\.dismiss) private var dismiss + + @State private var showAddAccount = false + @State private var isSwitching = false + @State private var switchError: String? + + var body: some View { + NavigationStack { + List { + Section { + ForEach(appState.accounts) { account in + Button { + guard account.id != appState.activeAccountID else { return } + switchTo(account) + } label: { + HStack { + Text(account.username) + .foregroundStyle(.primary) + Spacer() + if account.id == appState.activeAccountID { + Image(systemName: "checkmark") + .foregroundStyle(.tint) + } + } + } + .disabled(isSwitching) + } + .onDelete { indexSet in + for index in indexSet { + let account = appState.accounts[index] + Task { await appState.removeAccount(id: account.id) } + } + } + } + + Section { + Button { + showAddAccount = true + } label: { + Label("Add Account", systemImage: "plus.circle") + } + .disabled(isSwitching) + } + } + .navigationTitle("Accounts") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .confirmationAction) { + Button("Done") { dismiss() } + } + } + .overlay { + if isSwitching { + ZStack { + Color.black.opacity(0.25).ignoresSafeArea() + ProgressView("Switching…") + .padding() + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12)) + } + } + } + .alert("Switch Failed", isPresented: Binding( + get: { switchError != nil }, + set: { if !$0 { switchError = nil } } + )) { + Button("OK") { switchError = nil } + } message: { + Text(switchError ?? "") + } + .sheet(isPresented: $showAddAccount) { + AddAccountView() + } + } + } + + private func switchTo(_ account: AccountEntry) { + isSwitching = true + Task { + do { + try await appState.switchAccount(to: account.id) + dismiss() + } catch { + switchError = error.localizedDescription + } + isSwitching = false + } + } +} diff --git a/Hutch/Views/More/AddAccountView.swift b/Hutch/Views/More/AddAccountView.swift new file mode 100644 index 0000000..cfaa2b2 --- /dev/null +++ b/Hutch/Views/More/AddAccountView.swift @@ -0,0 +1,59 @@ +import SwiftUI + +struct AddAccountView: View { + @Environment(AppState.self) private var appState + @Environment(\.dismiss) private var dismiss + + @State private var token = "" + @State private var isConnecting = false + @State private var errorMessage: String? + + var body: some View { + NavigationStack { + Form { + Section { + SecureField("Personal Access Token", text: $token) + .autocorrectionDisabled() + .textInputAutocapitalization(.never) + } footer: { + Text("Generate a token at meta.sr.ht → OAuth2 clients.") + } + + if let errorMessage { + Section { + Text(errorMessage) + .foregroundStyle(.red) + } + } + } + .navigationTitle("Add Account") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .cancellationAction) { + Button("Cancel") { dismiss() } + .disabled(isConnecting) + } + ToolbarItem(placement: .confirmationAction) { + Button("Connect") { connect() } + .disabled(token.trimmingCharacters(in: .whitespaces).isEmpty || isConnecting) + } + } + .interactiveDismissDisabled(isConnecting) + } + } + + private func connect() { + isConnecting = true + errorMessage = nil + let trimmed = token.trimmingCharacters(in: .whitespaces) + Task { + do { + try await appState.addAccount(token: trimmed) + dismiss() + } catch { + errorMessage = error.localizedDescription + } + isConnecting = false + } + } +} diff --git a/Hutch/Views/More/MoreView.swift b/Hutch/Views/More/MoreView.swift index b7c5576..c981432 100644 --- a/Hutch/Views/More/MoreView.swift +++ b/Hutch/Views/More/MoreView.swift @@ -1,12 +1,16 @@ import SwiftUI struct MoreView: View { + @Environment(AppState.self) private var appState + private let unsupportedLinks: [(title: String, url: URL)] = [ ("chat.sr.ht", URL(string: "https://chat.sr.ht")!), ("man.sr.ht", URL(string: "https://man.sr.ht")!), ("srht.site", URL(string: "https://srht.site")!) ] + @State private var showAccountSwitcher = false + var body: some View { List { Section { @@ -36,5 +40,17 @@ struct MoreView: View { } } .navigationTitle("More") + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button { + showAccountSwitcher = true + } label: { + Image(systemName: "person.crop.circle") + } + } + } + .sheet(isPresented: $showAccountSwitcher) { + AccountSwitcherView() + } } } |
