krz/hutch

an ios client for sourcehut

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

v2.10.3: Hutch/Views/Settings/SettingsView.swift · raw

  1import SwiftUI
  2
  3struct SettingsView: View {
  4    @Environment(AppState.self) private var appState
  5    @AppStorage(AppStorageKeys.swipeActionsEnabled) private var swipeActionsEnabled = true
  6    @AppStorage(AppStorageKeys.contributionGraphsEnabled) private var contributionGraphsEnabled = true
  7    @State private var pendingDestructiveAction: SettingsDestructiveAction?
  8
  9    var body: some View {
 10        Form {
 11            behaviorSection()
 12            authenticationSection()
 13            aboutSection()
 14        }
 15        .navigationTitle("Settings")
 16        .alert(
 17            pendingDestructiveAction?.title ?? "",
 18            isPresented: Binding(
 19                get: { pendingDestructiveAction != nil },
 20                set: { isPresented in
 21                    if !isPresented {
 22                        pendingDestructiveAction = nil
 23                    }
 24                }
 25            )
 26        ) {
 27            Button("Cancel", role: .cancel) {}
 28            Button(pendingDestructiveAction?.confirmationLabel ?? "Confirm", role: .destructive) {
 29                guard let action = pendingDestructiveAction else { return }
 30                pendingDestructiveAction = nil
 31                Task {
 32                    switch action {
 33                    case .resetAppData:
 34                        await appState.resetAppData()
 35                    case .signOut:
 36                        await appState.signOut()
 37                    }
 38                }
 39            }
 40        } message: {
 41            if let pendingDestructiveAction {
 42                Text(pendingDestructiveAction.message)
 43            }
 44        }
 45    }
 46
 47    @ViewBuilder
 48    private func behaviorSection() -> some View {
 49        Section {
 50            Toggle("Swipe actions", isOn: $swipeActionsEnabled)
 51            Toggle("Contribution graphs", isOn: $contributionGraphsEnabled)
 52        } header: {
 53            Text("Behavior")
 54        } footer: {
 55            Text("When enabled, swipe list rows to quickly take actions like resolving tickets, cancelling builds, and deleting pastes. Contribution graphs controls whether SourceHut activity heatmaps appear in lookup profiles.")
 56        }
 57    }
 58
 59    @ViewBuilder
 60    private func authenticationSection() -> some View {
 61        Section {
 62            HStack {
 63                Image(systemName: "key.fill")
 64                    .foregroundStyle(.secondary)
 65                Text("Personal access token in use")
 66                    .font(.subheadline)
 67                    .foregroundStyle(.secondary)
 68            }
 69            .alignmentGuide(.listRowSeparatorLeading) { _ in 0 }
 70
 71            Button("Reset App Data", role: .destructive) {
 72                pendingDestructiveAction = .resetAppData
 73            }
 74
 75            Button("Sign Out", role: .destructive) {
 76                pendingDestructiveAction = .signOut
 77            }
 78        } header: {
 79            Text("Authentication")
 80        } footer: {
 81            Text("Hutch stores your SourceHut token in the iOS keychain. Reset App Data removes saved token data, local settings, cached responses, cookies, and embedded web data on this device.")
 82        }
 83    }
 84
 85    @ViewBuilder
 86    private func aboutSection() -> some View {
 87        Section("App") {
 88            NavigationLink {
 89                AboutView()
 90            } label: {
 91                SwiftUI.Label("About Hutch", systemImage: "info.circle")
 92            }
 93        }
 94    }
 95}
 96
 97func settingsBioAttributedString(_ markdown: String) -> AttributedString {
 98    profileBioAttributedString(markdown)
 99}
100
101private enum SettingsDestructiveAction {
102    case resetAppData
103    case signOut
104
105    var title: String {
106        switch self {
107        case .resetAppData:
108            "Reset App Data?"
109        case .signOut:
110            "Sign Out?"
111        }
112    }
113
114    var confirmationLabel: String {
115        switch self {
116        case .resetAppData:
117            "Reset App Data"
118        case .signOut:
119            "Sign Out"
120        }
121    }
122
123    var message: String {
124        switch self {
125        case .resetAppData:
126            "This signs you out and removes saved token data, local settings, cached responses, cookies, and embedded web content on this device."
127        case .signOut:
128            "This signs you out of Hutch and clears saved authentication state on this device."
129        }
130    }
131}
132
133private struct AboutView: View {
134    private let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
135        ?? Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String
136        ?? "Hutch"
137    private let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
138        ?? "Unknown"
139    private let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String
140        ?? "Unknown"
141
142    var body: some View {
143        Form {
144            Section {
145                VStack(alignment: .leading, spacing: 6) {
146                    Text(appName)
147                        .font(.title2.weight(.semibold))
148                    Text("A native SourceHut client for iOS.")
149                        .font(.subheadline)
150                        .foregroundStyle(.secondary)
151                }
152                .padding(.vertical, 4)
153
154                LabeledContent("Version", value: version)
155                LabeledContent("Build", value: build)
156            }
157
158            Section("Links") {
159                Link(destination: URL(string: "https://sr.ht")!) {
160                    SwiftUI.Label("SourceHut", systemImage: "link")
161                }
162                Link(destination: URL(string: "https://man.sr.ht")!) {
163                    SwiftUI.Label("SourceHut Manuals", systemImage: "book")
164                }
165                Link(destination: URL(string: "https://sr.ht/~ccleberg/Hutch")!) {
166                    SwiftUI.Label("Project Repository", systemImage: "folder")
167                }
168            }
169
170            Section("Support") {
171                Link(destination: URL(string: "mailto:hello@cleberg.net")!) {
172                    SwiftUI.Label("Email Support", systemImage: "envelope")
173                }
174            }
175
176            Section("Privacy") {
177                Text("Hutch uses your SourceHut personal access token to make requests on your behalf. The token is stored locally in the iOS keychain.")
178                    .font(.subheadline)
179                    .foregroundStyle(.secondary)
180
181                Link(destination: URL(string: "https://zerolabs.sh/hutch/privacy-policy/")!) {
182                    SwiftUI.Label("Privacy Policy", systemImage: "hand.raised")
183                }
184            }
185
186            Section("Acknowledgements") {
187                Text("Built for SourceHut users who want quick access to repositories, builds, and tickets on iOS.")
188                    .font(.subheadline)
189                    .foregroundStyle(.secondary)
190            }
191        }
192        .navigationTitle("About")
193        .navigationBarTitleDisplayMode(.inline)
194    }
195}