krz/hutch

an ios client for sourcehut

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

v2.13.1: 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                .onChange(of: contributionGraphsEnabled) { _, newValue in
 53                    ContributionWidgetContextStore.setEnabled(newValue)
 54                }
 55        } header: {
 56            Text("Behavior")
 57        } footer: {
 58            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.")
 59        }
 60    }
 61
 62    @ViewBuilder
 63    private func authenticationSection() -> some View {
 64        Section {
 65            HStack {
 66                Image(systemName: "key.fill")
 67                    .foregroundStyle(.secondary)
 68                Text("Personal access token in use")
 69                    .font(.subheadline)
 70                    .foregroundStyle(.secondary)
 71            }
 72            .alignmentGuide(.listRowSeparatorLeading) { _ in 0 }
 73
 74            Button("Reset App Data", role: .destructive) {
 75                pendingDestructiveAction = .resetAppData
 76            }
 77
 78            Button("Sign Out", role: .destructive) {
 79                pendingDestructiveAction = .signOut
 80            }
 81        } header: {
 82            Text("Authentication")
 83        } footer: {
 84            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.")
 85        }
 86    }
 87
 88    @ViewBuilder
 89    private func aboutSection() -> some View {
 90        Section("App") {
 91            NavigationLink {
 92                AboutView()
 93            } label: {
 94                SwiftUI.Label("About Hutch", systemImage: "info.circle")
 95            }
 96        }
 97    }
 98}
 99
100func settingsBioAttributedString(_ markdown: String) -> AttributedString {
101    profileBioAttributedString(markdown)
102}
103
104private enum SettingsDestructiveAction {
105    case resetAppData
106    case signOut
107
108    var title: String {
109        switch self {
110        case .resetAppData:
111            "Reset App Data?"
112        case .signOut:
113            "Sign Out?"
114        }
115    }
116
117    var confirmationLabel: String {
118        switch self {
119        case .resetAppData:
120            "Reset App Data"
121        case .signOut:
122            "Sign Out"
123        }
124    }
125
126    var message: String {
127        switch self {
128        case .resetAppData:
129            "This signs you out and removes saved token data, local settings, cached responses, cookies, and embedded web content on this device."
130        case .signOut:
131            "This signs you out of Hutch and clears saved authentication state on this device."
132        }
133    }
134}
135
136private struct AboutView: View {
137    private let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
138        ?? Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String
139        ?? "Hutch"
140    private let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
141        ?? "Unknown"
142    private let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String
143        ?? "Unknown"
144
145    var body: some View {
146        Form {
147            Section {
148                VStack(alignment: .leading, spacing: 6) {
149                    Text(appName)
150                        .font(.title2.weight(.semibold))
151                    Text("A native SourceHut client for iOS.")
152                        .font(.subheadline)
153                        .foregroundStyle(.secondary)
154                }
155                .padding(.vertical, 4)
156
157                LabeledContent("Version", value: version)
158                LabeledContent("Build", value: build)
159            }
160
161            Section("Links") {
162                Link(destination: URL(string: "https://sr.ht")!) {
163                    SwiftUI.Label("SourceHut", systemImage: "link")
164                }
165                Link(destination: URL(string: "https://man.sr.ht")!) {
166                    SwiftUI.Label("SourceHut Manuals", systemImage: "book")
167                }
168                Link(destination: URL(string: "https://sr.ht/~ccleberg/Hutch")!) {
169                    SwiftUI.Label("Project Repository", systemImage: "folder")
170                }
171            }
172
173            Section("Support") {
174                Link(destination: URL(string: "mailto:hello@cleberg.net")!) {
175                    SwiftUI.Label("Email Support", systemImage: "envelope")
176                }
177            }
178
179            Section("Privacy") {
180                Text("Hutch uses your SourceHut personal access token to make requests on your behalf. The token is stored locally in the iOS keychain.")
181                    .font(.subheadline)
182                    .foregroundStyle(.secondary)
183
184                Link(destination: URL(string: "https://zerolabs.sh/hutch/privacy-policy/")!) {
185                    SwiftUI.Label("Privacy Policy", systemImage: "hand.raised")
186                }
187            }
188
189            Section("Acknowledgements") {
190                Text("Built for SourceHut users who want quick access to repositories, builds, and tickets on iOS.")
191                    .font(.subheadline)
192                    .foregroundStyle(.secondary)
193            }
194        }
195        .navigationTitle("About")
196        .navigationBarTitleDisplayMode(.inline)
197    }
198}