From c19f23901b087fdfc090c129c94933ed1d6c1021 Mon Sep 17 00:00:00 2001 From: Mariano Belinky Date: Wed, 18 Feb 2026 19:01:02 +0000 Subject: [PATCH] iOS onboarding: prevent pairing flicker during auto-resume --- .../Onboarding/OnboardingWizardView.swift | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/apps/ios/Sources/Onboarding/OnboardingWizardView.swift b/apps/ios/Sources/Onboarding/OnboardingWizardView.swift index 2dda00f1852..c0e872b2ceb 100644 --- a/apps/ios/Sources/Onboarding/OnboardingWizardView.swift +++ b/apps/ios/Sources/Onboarding/OnboardingWizardView.swift @@ -1,4 +1,5 @@ import CoreImage +import Combine import OpenClawKit import PhotosUI import SwiftUI @@ -68,6 +69,7 @@ struct OnboardingWizardView: View { @State private var scannerError: String? @State private var selectedPhoto: PhotosPickerItem? @State private var lastPairingAutoResumeAttemptAt: Date? + private static let pairingAutoResumeTicker = Timer.publish(every: 2.0, on: .main, in: .common).autoconnect() let allowSkip: Bool let onClose: () -> Void @@ -271,6 +273,7 @@ struct OnboardingWizardView: View { } .onChange(of: self.appModel.gatewayServerName) { _, newValue in guard newValue != nil else { return } + self.showQRScanner = false self.statusLine = "Connected." if !self.didMarkCompleted, let selectedMode { OnboardingStateStore.markCompleted(mode: selectedMode) @@ -282,6 +285,9 @@ struct OnboardingWizardView: View { guard newValue == .active else { return } self.attemptAutomaticPairingResumeIfNeeded() } + .onReceive(Self.pairingAutoResumeTicker) { _ in + self.attemptAutomaticPairingResumeIfNeeded() + } } @ViewBuilder @@ -685,7 +691,16 @@ struct OnboardingWizardView: View { Task { await self.retryLastAttempt() } } + private func resumeAfterPairingApprovalInBackground() { + // Keep the pairing issue sticky to avoid visual flicker while we probe for approval. + self.appModel.gatewayAutoReconnectEnabled = true + self.appModel.gatewayPairingPaused = false + self.appModel.gatewayPairingRequestId = nil + Task { await self.retryLastAttempt(silent: true) } + } + private func attemptAutomaticPairingResumeIfNeeded() { + guard self.scenePhase == .active else { return } guard self.step == .auth else { return } guard self.issue.needsPairing else { return } guard self.connectingGatewayID == nil else { return } @@ -695,7 +710,7 @@ struct OnboardingWizardView: View { return } self.lastPairingAutoResumeAttemptAt = now - self.resumeAfterPairingApproval() + self.resumeAfterPairingApprovalInBackground() } private func detectQRCode(from data: Data) -> String? { @@ -837,11 +852,13 @@ struct OnboardingWizardView: View { await self.gatewayController.connectManual(host: host, port: self.manualPort, useTLS: self.manualTLS) } - private func retryLastAttempt() async { - self.connectingGatewayID = "retry" + private func retryLastAttempt(silent: Bool = false) async { + self.connectingGatewayID = silent ? "retry-auto" : "retry" // Keep current auth/pairing issue sticky while retrying to avoid Step 3 UI flip-flop. - self.connectMessage = "Retrying…" - self.statusLine = "Retrying last connection…" + if !silent { + self.connectMessage = "Retrying…" + self.statusLine = "Retrying last connection…" + } defer { self.connectingGatewayID = nil } await self.gatewayController.connectLastKnown() }