krz/hutch
an ios client for sourcehut
clone: git clone https://gitbay.org/krz/hutch.git
v2: Hutch/Views/Auth/AuthView.swift · raw
1import SwiftUI
2
3/// Token entry screen shown when the user is not authenticated.
4struct TokenEntryView: View {
5 private let createAccountURL = URL(string: "https://meta.sr.ht/register")!
6 private let personalAccessTokensURL = URL(string: "https://meta.sr.ht/oauth/personal-access-tokens")!
7
8 @Environment(AppState.self) private var appState
9 @State private var token = ""
10 @State private var isConnecting = false
11 @State private var errorMessage: String?
12
13 var body: some View {
14 NavigationStack {
15 Form {
16 Section {
17 Text("Enter your SourceHut personal access token to connect.")
18 } header: {
19 Text("Welcome to Hutch")
20 } footer: {
21 Text("Hutch stores your SourceHut personal access token securely in the iOS keychain.")
22 }
23
24 Section {
25 SecureField("Personal Access Token", text: $token)
26 .textContentType(.password)
27 .autocorrectionDisabled()
28 .textInputAutocapitalization(.never)
29 .disabled(isConnecting)
30 } header: {
31 Text("Token")
32 }
33
34 if let errorMessage {
35 Section {
36 Label {
37 Text(errorMessage)
38 } icon: {
39 Image(systemName: "exclamationmark.triangle.fill")
40 .foregroundStyle(.red)
41 }
42 .foregroundStyle(.red)
43 }
44 }
45
46 Section {
47 Button {
48 connect()
49 } label: {
50 HStack {
51 Text("Connect")
52 if isConnecting {
53 Spacer()
54 ProgressView()
55 }
56 }
57 }
58 .disabled(tokenTrimmed.isEmpty || isConnecting)
59 }
60
61 Section {
62 Link(destination: createAccountURL) {
63 Label("Create SourceHut account", systemImage: "person.badge.plus")
64 }
65
66 Link(destination: personalAccessTokensURL) {
67 Label("Create Personal Access Token", systemImage: "key")
68 }
69 } header: {
70 Text("Need an account?")
71 } footer: {
72 Text("These links open SourceHut in your browser. After signing up, create a Personal Access Token there and paste it here.")
73 }
74 }
75 .navigationTitle("Hutch")
76 }
77 }
78
79 private var tokenTrimmed: String {
80 token.trimmingCharacters(in: .whitespacesAndNewlines)
81 }
82
83 private func connect() {
84 errorMessage = nil
85 isConnecting = true
86 Task {
87 do {
88 try await appState.connect(with: tokenTrimmed)
89 } catch {
90 errorMessage = error.localizedDescription
91 }
92 isConnecting = false
93 }
94 }
95}