krz/hutch

an ios client for sourcehut

clone: git clone https://gitbay.org/krz/hutch.git

v3.1.6: 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}