import SwiftUI struct PairingView: View { var appState: AppState @Bindable var authViewModel: AuthViewModel @State private var email = "" @State private var password = "" @State private var pairingCode: String? @State private var isStarting = false @State private var isWaiting = false @State private var statusMessage: String? @State private var isError = false @Environment(\.dismiss) private var dismiss var body: some View { NavigationStack { ScrollView { VStack(spacing: 24) { Image(systemName: "iphone.and.arrow.forward") .font(.system(size: 48)) .foregroundStyle(.blue) Text("Device Pairing") .font(.title2.bold()) Text("Transfer your keys from an existing device to this one.") .font(.subheadline) .foregroundStyle(.secondary) .multilineTextAlignment(.center) if pairingCode == nil { // Phase 1: Enter email and start pairing VStack(spacing: 16) { // Server config DisclosureGroup("Server") { TextField("Host", text: $authViewModel.serverHost) .textContentType(.URL) .autocapitalization(.none) TextField("Port", text: $authViewModel.serverPort) .keyboardType(.numberPad) } TextField("Email", text: $email) .textContentType(.emailAddress) .keyboardType(.emailAddress) .autocapitalization(.none) .textFieldStyle(.roundedBorder) SecureField("Password (for key encryption)", text: $password) .textContentType(.password) .autocorrectionDisabled() .textInputAutocapitalization(.never) .textFieldStyle(.roundedBorder) Button("Start Pairing") { Task { await startPairing() } } .buttonStyle(.borderedProminent) .disabled(email.isEmpty || password.isEmpty || isStarting) if isStarting { ProgressView("Connecting...") } } } else { // Phase 2: Show code and wait for authorization VStack(spacing: 16) { Text("Pairing Code") .font(.headline) Text(pairingCode!) .font(.system(size: 36, weight: .bold, design: .monospaced)) .padding() .background(Color(.systemGray6)) .clipShape(RoundedRectangle(cornerRadius: 12)) Text("Enter this code on your already logged-in device\nto authorize this device.") .font(.caption) .foregroundStyle(.secondary) .multilineTextAlignment(.center) if isWaiting { ProgressView("Waiting for authorization...") .padding() } } } if let status = statusMessage { Text(status) .font(.caption) .foregroundStyle(isError ? .red : .green) .multilineTextAlignment(.center) } } .padding(32) } .navigationTitle("Pair Device") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarLeading) { Button("Cancel") { dismiss() } } } } } private func startPairing() async { isStarting = true isError = false statusMessage = nil // Connect to server if await !appState.chatClient.isConnected { do { let port = UInt16(authViewModel.serverPort) ?? Constants.defaultPort try await appState.chatClient.connect( host: authViewModel.serverHost, port: port ) } catch { isStarting = false statusMessage = "Connection failed: \(error.localizedDescription)" isError = true return } } let (success, codeOrMsg) = await appState.chatClient.pairingStart(email: email) isStarting = false if success { pairingCode = codeOrMsg // Start waiting for authorization isWaiting = true Task { await waitForAuthorization() } } else { statusMessage = codeOrMsg isError = true } } private func waitForAuthorization() async { let (success, msg) = await appState.chatClient.pairingWait( code: pairingCode!, email: email, password: password ) isWaiting = false if success { statusMessage = msg isError = false // Auto-login let (loginOk, loginMsg) = await appState.chatClient.login(email: email, password: password) if loginOk { appState.email = email appState.isLoggedIn = true appState.connectionStatus = .connected appState.startConnectionMonitor() if let userId = await appState.chatClient.userId { appState.currentUser = User( id: userId, username: await appState.chatClient.username, email: email ) } dismiss() } else { statusMessage = "Keys imported but login failed: \(loginMsg)" isError = true } } else { statusMessage = msg isError = true } } }