krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v3.6.0: Hutch/Views/More/AccountSwitcherView.swift · raw
1import SwiftUI
2
3struct AccountSwitcherView: View {
4 @Environment(AppState.self) private var appState
5 @Environment(\.dismiss) private var dismiss
6
7 @State private var showAddAccount = false
8 @State private var isSwitching = false
9 @State private var switchError: String?
10 @State private var pendingRemoval: AccountEntry?
11
12 var body: some View {
13 NavigationStack {
14 List {
15 Section {
16 ForEach(appState.accounts) { account in
17 let isActive = account.id == appState.activeAccountID
18 Button {
19 guard !isActive else { return }
20 switchTo(account)
21 } label: {
22 HStack(spacing: 12) {
23 if isActive {
24 Image(systemName: "person.crop.circle.fill")
25 .foregroundStyle(.tint)
26 } else {
27 Image(systemName: "person.crop.circle")
28 .foregroundStyle(.secondary)
29 }
30 VStack(alignment: .leading, spacing: 2) {
31 Text("~\(account.username)")
32 .foregroundStyle(.primary)
33 Text(isActive ? "Active Account" : "Tap to switch")
34 .font(.caption)
35 .foregroundStyle(.secondary)
36 }
37 Spacer()
38 if isActive {
39 Image(systemName: "checkmark")
40 .foregroundStyle(.tint)
41 }
42 }
43 }
44 .disabled(isSwitching)
45 }
46 .onDelete { indexSet in
47 for index in indexSet {
48 pendingRemoval = appState.accounts[index]
49 }
50 }
51 .themedRow()
52 }
53
54 Section {
55 Button {
56 showAddAccount = true
57 } label: {
58 Label("Add Account", systemImage: "plus.circle")
59 }
60 .disabled(isSwitching)
61 .themedRow()
62 }
63 }
64 .themedList()
65 .navigationTitle("Accounts")
66 .navigationBarTitleDisplayMode(.inline)
67 .toolbar {
68 ToolbarItem(placement: .confirmationAction) {
69 Button("Done") { dismiss() }
70 }
71 }
72 .overlay {
73 if isSwitching {
74 ZStack {
75 Color.black.opacity(0.25).ignoresSafeArea()
76 ProgressView("Switching…")
77 .padding()
78 .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12))
79 }
80 }
81 }
82 .alert("Switch Failed", isPresented: Binding(
83 get: { switchError != nil },
84 set: { if !$0 { switchError = nil } }
85 )) {
86 Button("OK") { switchError = nil }
87 } message: {
88 Text(switchError ?? "")
89 }
90 .alert(
91 "Remove Account?",
92 isPresented: Binding(
93 get: { pendingRemoval != nil },
94 set: { isPresented in
95 if !isPresented {
96 pendingRemoval = nil
97 }
98 }
99 )
100 ) {
101 Button("Cancel", role: .cancel) {
102 /* Dismiss only; removal uses the destructive button. */
103 }
104 Button("Remove", role: .destructive) {
105 guard let pendingRemoval else { return }
106 Task { await appState.removeAccount(id: pendingRemoval.id) }
107 self.pendingRemoval = nil
108 }
109 } message: {
110 if let pendingRemoval {
111 Text("~\(pendingRemoval.username) and its isolated local cache will be removed from this device.")
112 }
113 }
114 .sheet(isPresented: $showAddAccount) {
115 AddAccountView()
116 }
117 }
118 }
119
120 private func switchTo(_ account: AccountEntry) {
121 isSwitching = true
122 Task {
123 do {
124 try await appState.switchAccount(to: account.id)
125 dismiss()
126 } catch {
127 switchError = error.localizedDescription
128 }
129 isSwitching = false
130 }
131 }
132}