// // LoginView.swift // AT-IRC // import SwiftUI struct LoginView: View { @Bindable var store: ChatStore @State private var handle = "" @State private var authURL: IdentifiableURL? @State private var errorText: String? @State private var isBusy = false var body: some View { VStack(spacing: 24) { Spacer() Image(systemName: "bubble.left.and.bubble.right.fill") .font(.system(size: 56)) .foregroundStyle(.tint) Text("AT-IRC") .font(.largeTitle.bold()) Text("Sign in with your atproto account to join the chat.") .font(.subheadline) .foregroundStyle(.secondary) .multilineTextAlignment(.center) TextField("handle (e.g. alice.bsky.social)", text: $handle) .textInputAutocapitalization(.never) .autocorrectionDisabled() .keyboardType(.URL) .textFieldStyle(.roundedBorder) .disabled(isBusy) Button(action: signIn) { HStack(spacing: 8) { if isBusy { ProgressView() .controlSize(.small) } Text("Sign in with Bluesky") } .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) .controlSize(.large) .disabled(trimmedHandle.isEmpty || isBusy) if let errorText { Text(errorText) .font(.footnote) .foregroundStyle(.red) .multilineTextAlignment(.center) } Spacer() } .padding(24) .sheet(item: $authURL) { item in SafariView(url: item.url) .ignoresSafeArea() } .task { autoSignInIfRequested() } } /// Simulator sign-in automation. See SignInTestHook and scripts/sim-signin.sh. private func autoSignInIfRequested() { #if DEBUG guard let auto = SignInTestHook.autoHandle, handle.isEmpty, !isBusy else { return } handle = auto signIn() #endif } private var trimmedHandle: String { handle.trimmingCharacters(in: .whitespacesAndNewlines) } private func signIn() { isBusy = true errorText = nil Task { do { try await store.signIn(handle: trimmedHandle) { url in #if DEBUG if SignInTestHook.isEnabled { // The headless browser is the only one to open the // authorization request; the PAR request_uri is // single-use ("initiated from another device"). SignInTestHook.writeAuthorizationURL(url) return } #endif authURL = IdentifiableURL(url: url) } #if DEBUG if SignInTestHook.isEnabled { SignInTestHook.finish(.success(store)) } #endif } catch { errorText = error.localizedDescription #if DEBUG if SignInTestHook.isEnabled { SignInTestHook.finish(.failure(error)) } #endif } authURL = nil isBusy = false } } }