import SwiftUI struct SettingsView: View { @Environment(\.dismiss) private var dismiss @State private var instanceURL: String @State private var token: String @State private var mergeGateMode: MergeGateMode @State private var testResult: TestResult? @State private var testing = false @State private var showLogoutConfirm = false enum TestResult: Equatable { case success(String) case failure(String) } init() { _instanceURL = State(initialValue: SettingsStore.shared.instanceURL) _token = State(initialValue: SettingsStore.shared.token) _mergeGateMode = State(initialValue: SettingsStore.shared.mergeGateMode) } var body: some View { NavigationStack { Form { Section("Server") { TextField("Instance URL", text: $instanceURL) .keyboardType(.URL) .autocapitalization(.none) .autocorrectionDisabled() .textInputAutocapitalization(.never) SecureField("Personal access token", text: $token) Button(testing ? "Testing…" : "Test connection") { test() } .disabled(testing || token.isEmpty) if let testResult { testResultView(testResult) } } Section("Merge gating") { Picker("Merge behavior", selection: $mergeGateMode) { ForEach(MergeGateMode.allCases) { m in Text(m.rawValue).tag(m) } } .pickerStyle(.inline) Text("Warn mode shows CI status and allows merging a red PR after confirmation. Block mode disables merge while any check is failing.") .font(.caption) .foregroundColor(.secondary) } Section { Button("Log out", role: .destructive) { showLogoutConfirm = true } .disabled(token.isEmpty) } } .navigationTitle("Settings") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } } ToolbarItem(placement: .confirmationAction) { Button("Save") { save() } .disabled(instanceURL.trimmingCharacters(in: .whitespaces) .isEmpty) } } .confirmationDialog("Log out?", isPresented: $showLogoutConfirm, titleVisibility: .visible) { Button("Log out", role: .destructive) { SettingsStore.shared.logout() testResult = nil dismiss() } Button("Cancel", role: .cancel) {} } } } @ViewBuilder private func testResultView(_ result: TestResult) -> some View { switch result { case .success(let login): Label("Connected as \(login)", systemImage: "checkmark.circle.fill") .font(.callout) .foregroundColor(.green) case .failure(let message): Label(message, systemImage: "xmark.circle.fill") .font(.callout) .foregroundColor(.red) } } private func test() { testing = true testResult = nil let trimmed = instanceURL.trimmingCharacters(in: .whitespacesAndNewlines) guard let url = URL(string: trimmed) else { testResult = .failure("Invalid URL") testing = false return } let client = GiteaClient(instanceURL: url, token: token) Task { do { let user = try await client.currentUser() AppSession.currentLogin = user.login testResult = .success(user.login) } catch { testResult = .failure(error.localizedDescription) } testing = false } } private func save() { SettingsStore.shared.instanceURL = instanceURL.trimmingCharacters(in: .whitespacesAndNewlines) SettingsStore.shared.token = token SettingsStore.shared.mergeGateMode = mergeGateMode dismiss() } }