Skip to content

Commit 1fcc85e

Browse files
committed
feat(ios): refine control and talk visual hierarchy
1 parent 5a5913a commit 1fcc85e

10 files changed

Lines changed: 170 additions & 84 deletions

apps/ios/Sources/Design/OpenClawProComponents.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,18 @@ struct ProSectionHeader: View {
3232
var body: some View {
3333
HStack {
3434
Text(self.title)
35-
.font(.caption.weight(.medium))
35+
.font(.footnote.weight(.medium))
3636
.foregroundStyle(.secondary)
3737
.textCase(self.uppercase ? .uppercase : nil)
3838
Spacer()
3939
if let actionTitle {
4040
if let action {
4141
Button(actionTitle, action: action)
42-
.font(.caption.weight(.medium))
42+
.font(.footnote.weight(.medium))
4343
.foregroundStyle(OpenClawBrand.accent)
4444
} else {
4545
Text(actionTitle)
46-
.font(.caption.weight(.medium))
46+
.font(.footnote.weight(.medium))
4747
.foregroundStyle(.secondary)
4848
}
4949
}
@@ -377,7 +377,7 @@ struct ProValuePill: View {
377377

378378
var body: some View {
379379
Text(self.value)
380-
.font(.caption.weight(.semibold))
380+
.font(.footnote.weight(.semibold))
381381
.foregroundStyle(self.color)
382382
.lineLimit(1)
383383
.padding(.horizontal, 8)

apps/ios/Sources/Design/RootTabsPhoneControlHub.swift

Lines changed: 6 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ struct RootTabsPhoneControlHub: View {
2121
ForEach(self.groups) { group in
2222
self.groupSection(group)
2323
}
24-
self.versionFooter
2524
}
2625
.padding(.vertical, self.isCompactHeight ? 10 : 16)
2726
}
@@ -51,7 +50,7 @@ struct RootTabsPhoneControlHub: View {
5150
.font(.subheadline.weight(.semibold))
5251
.lineLimit(1)
5352
Text(self.gatewayDisplayLabel)
54-
.font(.caption)
53+
.font(.footnote)
5554
.foregroundStyle(.secondary)
5655
.lineLimit(1)
5756
.truncationMode(.middle)
@@ -71,7 +70,7 @@ struct RootTabsPhoneControlHub: View {
7170
.font(.headline)
7271
.lineLimit(1)
7372
Text(self.gatewayDisplayLabel)
74-
.font(.caption)
73+
.font(.footnote)
7574
.foregroundStyle(.secondary)
7675
.lineLimit(1)
7776
.truncationMode(.middle)
@@ -98,14 +97,14 @@ struct RootTabsPhoneControlHub: View {
9897
.font(.subheadline.weight(.semibold))
9998
.foregroundStyle(.primary)
10099
Text(self.gatewayDisplayLabel)
101-
.font(.caption)
100+
.font(.footnote)
102101
.foregroundStyle(.secondary)
103102
.lineLimit(1)
104103
.truncationMode(.middle)
105104
}
106105
Spacer(minLength: 8)
107106
Text(self.gatewayActionTitle)
108-
.font(.caption.weight(.semibold))
107+
.font(.footnote.weight(.semibold))
109108
.foregroundStyle(OpenClawBrand.accent)
110109
Image(systemName: "chevron.right")
111110
.font(.caption2.weight(.bold))
@@ -157,13 +156,13 @@ struct RootTabsPhoneControlHub: View {
157156

158157
private func rowLabel(_ destination: RootTabs.SidebarDestination) -> some View {
159158
HStack(alignment: .center, spacing: 12) {
160-
ProIconBadge(systemName: destination.systemImage, color: self.color(for: destination))
159+
ProIconBadge(systemName: destination.systemImage, color: .secondary)
161160
VStack(alignment: .leading, spacing: 3) {
162161
Text(destination.title)
163162
.font(.subheadline.weight(.semibold))
164163
.foregroundStyle(.primary)
165164
Text(destination.subtitle)
166-
.font(.caption)
165+
.font(.footnote)
167166
.foregroundStyle(.secondary)
168167
.lineLimit(1)
169168
}
@@ -177,20 +176,6 @@ struct RootTabsPhoneControlHub: View {
177176
.contentShape(Rectangle())
178177
}
179178

180-
private var versionFooter: some View {
181-
ProCard(radius: OpenClawProMetric.cardRadius) {
182-
HStack {
183-
Spacer()
184-
Text("v\(DeviceInfoHelper.openClawVersionString())")
185-
.font(.caption.weight(.semibold))
186-
.foregroundStyle(.secondary)
187-
.lineLimit(1)
188-
Spacer()
189-
}
190-
}
191-
.padding(.horizontal, OpenClawProMetric.pagePadding)
192-
}
193-
194179
@ViewBuilder
195180
private func detail(for destination: RootTabs.SidebarDestination) -> some View {
196181
switch destination {
@@ -350,19 +335,6 @@ struct RootTabsPhoneControlHub: View {
350335
verticalSizeClass == .compact ? 72 : 112
351336
}
352337

353-
private func color(for destination: RootTabs.SidebarDestination) -> Color {
354-
switch destination {
355-
case .chat, .talk, .overview, .gateway:
356-
OpenClawBrand.accent
357-
case .instances:
358-
Color.secondary
359-
case .activity, .usage, .docs:
360-
OpenClawBrand.accentHot
361-
case .agents, .workboard, .skillWorkshop, .sessions, .dreaming, .cron, .settings:
362-
OpenClawBrand.ok
363-
}
364-
}
365-
366338
private func resolveDefaultAgentID() -> String {
367339
self.normalized(self.appModel.gatewayDefaultAgentId) ?? ""
368340
}

apps/ios/Sources/Design/SettingsProTab.swift

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,11 @@ struct SettingsProTab: View {
222222
}
223223
}
224224
}
225+
.sheet(isPresented: self.$showNotificationRelayDisclosure) {
226+
HostedPushRelayDisclosureSheet(
227+
message: self.notificationRelayDisclosureMessage,
228+
onContinue: self.requestNotificationAuthorizationFromSettings)
229+
}
225230
.alert("Reset Onboarding?", isPresented: self.$showResetOnboardingAlert) {
226231
Button("Reset", role: .destructive) {
227232
self.resetOnboarding()
@@ -240,14 +245,6 @@ struct SettingsProTab: View {
240245
} message: {
241246
Text(self.scannerError ?? "")
242247
}
243-
.alert("Enable OpenClaw Hosted Push Relay?", isPresented: self.$showNotificationRelayDisclosure) {
244-
Button("Continue") {
245-
self.requestNotificationAuthorizationFromSettings()
246-
}
247-
Button("Not Now", role: .cancel) {}
248-
} message: {
249-
Text(self.notificationRelayDisclosureMessage)
250-
}
251248
}
252249

253250
func openNotificationsRouteFromApprovals() {
@@ -274,3 +271,45 @@ struct SettingsProTab: View {
274271
self.onRouteChange?(self.navigationPath.last)
275272
}
276273
}
274+
275+
struct HostedPushRelayDisclosureSheet: View {
276+
@Environment(\.dismiss) private var dismiss
277+
let message: String
278+
let onContinue: () -> Void
279+
280+
var body: some View {
281+
VStack(spacing: 18) {
282+
ScrollView {
283+
VStack(alignment: .leading, spacing: 18) {
284+
Image(systemName: "network")
285+
.font(.title2.weight(.semibold))
286+
.foregroundStyle(Color(uiColor: .systemBlue))
287+
Text("Enable OpenClaw Hosted Push Relay?")
288+
.font(.title3.weight(.semibold))
289+
Text(self.message)
290+
.font(.body)
291+
.foregroundStyle(.secondary)
292+
}
293+
.frame(maxWidth: .infinity, alignment: .leading)
294+
}
295+
VStack(spacing: 10) {
296+
Button {
297+
self.dismiss()
298+
self.onContinue()
299+
} label: {
300+
Text("Continue").frame(maxWidth: .infinity)
301+
}
302+
.buttonStyle(.borderedProminent)
303+
Button("Not Now", role: .cancel) {
304+
self.dismiss()
305+
}
306+
.buttonStyle(.bordered)
307+
.frame(maxWidth: .infinity)
308+
}
309+
}
310+
.tint(Color(uiColor: .systemBlue))
311+
.padding(24)
312+
.presentationDetents([.medium, .large])
313+
.presentationDragIndicator(.visible)
314+
}
315+
}

apps/ios/Sources/Design/SettingsProTabSections.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ extension SettingsProTab {
190190
Text(title)
191191
.font(.subheadline.weight(.semibold))
192192
Text(detail)
193-
.font(.caption2)
193+
.font(.footnote)
194194
.foregroundStyle(.secondary)
195195
.lineLimit(1)
196196
}

apps/ios/Sources/Design/TalkProTab.swift

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ struct TalkProTab: View {
175175
.background {
176176
RoundedRectangle(cornerRadius: 14, style: .continuous)
177177
.fill(self.state.primaryButtonFill)
178-
.shadow(color: self.state.color.opacity(0.28), radius: 18, y: 8)
178+
.shadow(color: self.state.primaryButtonFill.opacity(0.22), radius: 18, y: 8)
179179
}
180180
}
181181
.buttonStyle(.plain)
@@ -607,17 +607,14 @@ struct TalkProState: Equatable {
607607
}
608608
}
609609

610-
var primaryButtonFill: AnyShapeStyle {
610+
var primaryButtonFill: Color {
611611
switch self.primaryAction {
612612
case .stop:
613-
AnyShapeStyle(OpenClawBrand.danger)
613+
OpenClawBrand.danger
614614
case .waiting:
615-
AnyShapeStyle(OpenClawBrand.warn.opacity(0.72))
615+
OpenClawBrand.warn.opacity(0.72)
616616
default:
617-
AnyShapeStyle(LinearGradient(
618-
colors: [self.color.opacity(0.95), OpenClawBrand.accent],
619-
startPoint: .topLeading,
620-
endPoint: .bottomTrailing))
617+
Color(uiColor: .systemBlue)
621618
}
622619
}
623620

@@ -662,7 +659,7 @@ private struct TalkProOrb: View {
662659
var body: some View {
663660
TimelineView(.periodic(from: .now, by: 1.0 / 24.0)) { timeline in
664661
ZStack {
665-
ForEach(0..<3, id: \.self) { ring in
662+
ForEach(0..<self.ringCount, id: \.self) { ring in
666663
Circle()
667664
.strokeBorder(self.color.opacity(self.ringOpacity(ring)), lineWidth: 1.4)
668665
.scaleEffect(self.ringScale(ring, date: timeline.date))
@@ -676,16 +673,25 @@ private struct TalkProOrb: View {
676673
}
677674
TalkProWaveform(mode: self.mode, tint: self.color, barCount: 18)
678675
.frame(width: 116, height: 52)
679-
.opacity(self.systemImage == "waveform" || self.systemImage == "mic.fill" ? 1 : 0.34)
676+
.opacity(self.showsWaveform ? 1 : 0)
680677
Image(systemName: self.systemImage)
681678
.font(.system(size: 34, weight: .bold))
682679
.foregroundStyle(self.color)
683-
.opacity(self.systemImage == "waveform" || self.systemImage == "mic.fill" ? 0.20 : 1)
680+
.opacity(self.showsWaveform ? 0.20 : 1)
684681
}
685682
.frame(maxWidth: .infinity, maxHeight: .infinity)
683+
.clipped()
686684
}
687685
}
688686

687+
private var ringCount: Int {
688+
self.mode == .still ? 0 : 3
689+
}
690+
691+
private var showsWaveform: Bool {
692+
self.systemImage == "waveform" || self.systemImage == "mic.fill"
693+
}
694+
689695
private func ringScale(_ ring: Int, date: Date) -> CGFloat {
690696
guard !self.reduceMotion else { return CGFloat(1.0 + (Double(ring) * 0.12)) }
691697
let base = 0.88 + (Double(ring) * 0.18)

apps/ios/Sources/OpenClawApp.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,10 @@ struct OpenClawApp: App {
630630
GatewaySettingsStore.bootstrapPersistence()
631631
let appModel = NodeAppModel()
632632
#if DEBUG
633+
if ProcessInfo.processInfo.arguments.contains("--openclaw-reset-onboarding") {
634+
// Reruns must exercise onboarding instead of saved pairing state.
635+
GatewayOnboardingReset.reset(appModel: appModel, instanceId: GatewaySettingsStore.currentInstanceID())
636+
}
633637
if Self.screenshotModeEnabled {
634638
UIView.setAnimationsEnabled(false)
635639
UserDefaults.standard.set(true, forKey: "gateway.onboardingComplete")

apps/ios/Sources/RootTabs.swift

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,6 @@ struct RootTabs: View {
269269
VStack(spacing: 0) {
270270
self.sidebarIdentityHeader
271271
self.sidebarList
272-
self.sidebarFooter
273272
}
274273
.safeAreaPadding(.top, 8)
275274
.safeAreaPadding(.bottom, 8)
@@ -344,26 +343,6 @@ struct RootTabs: View {
344343
.background(Color(uiColor: .systemBackground))
345344
}
346345

347-
private var sidebarFooter: some View {
348-
VStack(spacing: 0) {
349-
self.sidebarHorizontalSeparator
350-
HStack(spacing: 10) {
351-
Text("VERSION")
352-
.font(.caption2.weight(.semibold))
353-
.foregroundStyle(.secondary)
354-
Spacer(minLength: 8)
355-
Text("v\(DeviceInfoHelper.openClawVersionString())")
356-
.font(.caption.weight(.semibold))
357-
.foregroundStyle(.primary)
358-
.lineLimit(1)
359-
.minimumScaleFactor(0.72)
360-
ProStatusDot(color: self.sidebarGatewayStatusColor)
361-
}
362-
.padding(.horizontal, 18)
363-
.padding(.vertical, 12)
364-
}
365-
}
366-
367346
private var sidebarHorizontalSeparator: some View {
368347
Rectangle()
369348
.fill(Color(uiColor: .separator))

apps/ios/Tests/RootTabsSourceGuardTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ struct RootTabsSourceGuardTests {
118118
#expect(source.contains(".listRowSeparator(.hidden, edges: .all)"))
119119
#expect(source.contains(".listSectionSeparator(.hidden, edges: .all)"))
120120
#expect(source.contains("if self.isSidebarDrawerLayout {"))
121-
#expect(source.contains("private var sidebarFooter: some View"))
121+
#expect(!source.contains("private var sidebarFooter: some View"))
122122
#expect(!source.contains("LabeledContent(\"Version\""))
123123
#expect(navigationSource.contains("SidebarGroup(title: \"CHAT\", destinations: [.chat, .talk])"))
124124
#expect(!navigationSource.contains("title: \"AGENT\""))

apps/ios/Tests/SwiftUIRenderSmokeTests.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ import UIKit
4242
}
4343
}
4444

45+
@Test @MainActor func hostedPushRelayDisclosureBuildsAViewHierarchy() {
46+
for typeSize in [DynamicTypeSize.large, .accessibility5] {
47+
let root = HostedPushRelayDisclosureSheet(
48+
message: "Enabling this sends delivery data through OpenClaw's hosted push relay.",
49+
onContinue: {})
50+
.environment(\.dynamicTypeSize, typeSize)
51+
52+
_ = Self.host(root, size: CGSize(width: 402, height: 450))
53+
}
54+
}
55+
4556
@Test @MainActor func rootTabsBuildsDeviceOrientationShellMatrix() {
4657
for scenario in Self.rootTabsShellScenarios() {
4758
let appModel = NodeAppModel()

0 commit comments

Comments
 (0)