Files
openclaw/apps/ios/Sources/Gateway/GatewayQuickSetupSheet.swift
Sash Zats c5b79e3b7a fix(ios): fix quick setup sheet layout design (#93751)
* fix(ios): simplify quick setup sheet layout

* fix(ios): restore quick setup card chrome
2026-06-17 04:45:24 +08:00

198 lines
8.0 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import OpenClawKit
import SwiftUI
struct GatewayQuickSetupSheet: View {
@Environment(NodeAppModel.self) private var appModel
@Environment(GatewayConnectionController.self) private var gatewayController
@Environment(\.dismiss) private var dismiss
@AppStorage("onboarding.quickSetupDismissed") private var quickSetupDismissed: Bool = false
@State private var connecting: Bool = false
@State private var connectError: String?
@State private var showGatewayProblemDetails: Bool = false
var body: some View {
NavigationStack {
VStack(alignment: .leading, spacing: 16) {
Text("Connect to a Gateway?")
.font(.title2.bold())
if let gatewayProblem = self.appModel.lastGatewayProblem {
GatewayProblemBanner(
problem: gatewayProblem,
primaryActionTitle: self.gatewayProblemPrimaryActionTitle(gatewayProblem),
onPrimaryAction: {
Task { await self.handleGatewayProblemPrimaryAction(gatewayProblem) }
},
onShowDetails: {
self.showGatewayProblemDetails = true
})
}
if let candidate = self.bestCandidate {
GatewayQuickSetupCandidatePanel(
name: candidate.name,
debugID: candidate.debugID,
discoveryStatusText: self.gatewayController.discoveryStatusText,
gatewayDisplayStatusText: self.appModel.gatewayDisplayStatusText,
nodeStatusText: self.appModel.nodeStatusText,
operatorStatusText: self.appModel.operatorStatusText)
Button {
self.connectError = nil
self.connecting = true
Task {
let err = await self.gatewayController.connectWithDiagnostics(candidate)
await MainActor.run {
self.connecting = false
self.connectError = err
// If we kicked off a connect, leave the sheet up so the user can see status evolve.
}
}
} label: {
Group {
if self.connecting {
HStack(spacing: 8) {
ProgressView().progressViewStyle(.circular)
Text("Connecting…")
}
} else {
Text("Connect")
}
}
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.disabled(self.connecting)
if let connectError {
Text(connectError)
.font(.footnote)
.foregroundStyle(.secondary)
.textSelection(.enabled)
}
Button {
self.dismiss()
} label: {
Text("Not now")
.frame(maxWidth: .infinity)
}
.buttonStyle(.bordered)
.disabled(self.connecting)
self.fullRowToggle("Dont show this again", isOn: self.$quickSetupDismissed)
.padding(.top, 4)
} else {
Text("No gateways found yet. Make sure your gateway is running and Bonjour discovery is enabled.")
.foregroundStyle(.secondary)
}
Spacer()
}
.padding()
.navigationTitle("Quick Setup")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button {
self.quickSetupDismissed = true
self.dismiss()
} label: {
Text("Close")
}
}
}
}
.sheet(isPresented: self.$showGatewayProblemDetails) {
if let gatewayProblem = self.appModel.lastGatewayProblem {
GatewayProblemDetailsSheet(
problem: gatewayProblem,
primaryActionTitle: self.gatewayProblemPrimaryActionTitle(gatewayProblem),
onPrimaryAction: {
Task { await self.handleGatewayProblemPrimaryAction(gatewayProblem) }
})
}
}
}
private var bestCandidate: GatewayDiscoveryModel.DiscoveredGateway? {
// Prefer whatever discovery says is first; the list is already name-sorted.
self.gatewayController.gateways.first
}
private func fullRowToggle(_ title: String, isOn: Binding<Bool>) -> some View {
Toggle(title, isOn: isOn)
.contentShape(Rectangle())
.overlay {
// Keep Toggle semantics for accessibility while making the full visual row tappable.
Button {
isOn.wrappedValue.toggle()
} label: {
Rectangle()
.fill(.clear)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.accessibilityHidden(true)
}
}
private func gatewayProblemPrimaryActionTitle(_ problem: GatewayConnectionProblem) -> String {
problem.canTrustRotatedCertificate ? "Trust certificate" : "Connect"
}
private func handleGatewayProblemPrimaryAction(_ problem: GatewayConnectionProblem) async {
if problem.canTrustRotatedCertificate {
_ = await self.gatewayController.trustRotatedGatewayCertificate(from: problem)
return
}
guard let candidate = self.bestCandidate else { return }
self.connectError = nil
self.connecting = true
let err = await self.gatewayController.connectWithDiagnostics(candidate)
self.connecting = false
self.connectError = err
}
}
private struct GatewayQuickSetupCandidatePanel: View {
private static let readableMonospaceWidth: CGFloat = 72 * 8
let name: String
let debugID: String
let discoveryStatusText: String
let gatewayDisplayStatusText: String
let nodeStatusText: String
let operatorStatusText: String
var body: some View {
VStack(alignment: .leading, spacing: 6) {
Text(verbatim: self.name)
.font(.system(.headline, design: .monospaced))
.foregroundStyle(.primary)
Text(verbatim: self.debugID)
.font(.system(.footnote, design: .monospaced))
.foregroundStyle(.secondary)
VStack(alignment: .leading, spacing: 2) {
// Use verbatim strings so Bonjour-provided values can't be interpreted as
// localized format strings (which can crash with Objective-C exceptions).
Text(verbatim: "Discovery: \(self.discoveryStatusText)")
Text(verbatim: "Status: \(self.gatewayDisplayStatusText)")
Text(verbatim: "Node: \(self.nodeStatusText)")
Text(verbatim: "Operator: \(self.operatorStatusText)")
}
.font(.system(.footnote, design: .monospaced))
.foregroundStyle(.secondary)
}
.frame(maxWidth: Self.readableMonospaceWidth, alignment: .leading)
.padding(.vertical, 14)
.padding(.horizontal, 16)
.frame(maxWidth: .infinity, alignment: .leading)
.textSelection(.enabled)
.background(.thinMaterial)
.clipShape(RoundedRectangle(cornerRadius: 14))
}
}