Files
openclaw/apps/ios/Sources/Onboarding/QRScannerView.swift
Mariano 130e59a9c0 iOS: port onboarding + QR pairing flow stability (#18162)
Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: a87eadea19
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: mbelinky <132747814+mbelinky@users.noreply.github.com>
Reviewed-by: @mbelinky
2026-02-16 16:22:51 +00:00

97 lines
3.4 KiB
Swift

import OpenClawKit
import SwiftUI
import VisionKit
struct QRScannerView: UIViewControllerRepresentable {
let onGatewayLink: (GatewayConnectDeepLink) -> Void
let onError: (String) -> Void
let onDismiss: () -> Void
func makeUIViewController(context: Context) -> UIViewController {
guard DataScannerViewController.isSupported else {
context.coordinator.reportError("QR scanning is not supported on this device.")
return UIViewController()
}
guard DataScannerViewController.isAvailable else {
context.coordinator.reportError("Camera scanning is currently unavailable.")
return UIViewController()
}
let scanner = DataScannerViewController(
recognizedDataTypes: [.barcode(symbologies: [.qr])],
isHighlightingEnabled: true)
scanner.delegate = context.coordinator
do {
try scanner.startScanning()
} catch {
context.coordinator.reportError("Could not start QR scanner.")
}
return scanner
}
func updateUIViewController(_: UIViewController, context _: Context) {}
static func dismantleUIViewController(_ uiViewController: UIViewController, coordinator: Coordinator) {
if let scanner = uiViewController as? DataScannerViewController {
scanner.stopScanning()
}
coordinator.parent.onDismiss()
}
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
final class Coordinator: NSObject, DataScannerViewControllerDelegate {
let parent: QRScannerView
private var handled = false
private var reportedError = false
init(parent: QRScannerView) {
self.parent = parent
}
func reportError(_ message: String) {
guard !self.reportedError else { return }
self.reportedError = true
Task { @MainActor in
self.parent.onError(message)
}
}
func dataScanner(_: DataScannerViewController, didAdd items: [RecognizedItem], allItems _: [RecognizedItem]) {
guard !self.handled else { return }
for item in items {
guard case let .barcode(barcode) = item,
let payload = barcode.payloadStringValue
else { continue }
// Try setup code format first (base64url JSON from /pair qr).
if let link = GatewayConnectDeepLink.fromSetupCode(payload) {
self.handled = true
self.parent.onGatewayLink(link)
return
}
// Fall back to deep link URL format (openclaw://gateway?...).
if let url = URL(string: payload),
let route = DeepLinkParser.parse(url),
case let .gateway(link) = route
{
self.handled = true
self.parent.onGatewayLink(link)
return
}
}
}
func dataScanner(_: DataScannerViewController, didRemove _: [RecognizedItem], allItems _: [RecognizedItem]) {}
func dataScanner(
_: DataScannerViewController,
becameUnavailableWithError _: DataScannerViewController.ScanningUnavailable)
{
self.reportError("Camera is not available on this device.")
}
}
}