fix(ios): brand remaining SwiftUI controls

This commit is contained in:
joshavant
2026-07-03 13:38:44 -05:00
committed by Josh Avant
parent d483cc1f72
commit 17f152ddd0
18 changed files with 336 additions and 82 deletions
@@ -49,6 +49,7 @@ struct AgentProDreamingDestination: View {
self.dreamingPhasesCard
}
.padding(.vertical, 18)
.font(OpenClawType.body)
}
.refreshable {
await self.refresh()
@@ -194,6 +195,7 @@ struct AgentProDreamingDestination: View {
Task { await self.runDreamAction(action) }
} label: {
Label(action.title, systemImage: self.dreamActionBusy == action ? "hourglass" : action.icon)
.font(OpenClawType.captionSemiBold)
}
.buttonStyle(.bordered)
.controlSize(.small)
@@ -23,6 +23,7 @@ struct AgentProNodesDestination: View {
self.nodesList
}
.padding(.vertical, 18)
.font(OpenClawType.body)
}
.refreshable {
await self.refresh()
@@ -208,6 +209,7 @@ struct AgentProNodesDestination: View {
self.nodeListCard(title: "Tags", values: entry.tags ?? [])
}
.padding(.vertical, 18)
.font(OpenClawType.body)
}
.safeAreaPadding(.bottom, OpenClawProMetric.bottomScrollInset)
}
@@ -98,6 +98,7 @@ extension AgentProTab {
Task { await self.runCronJob(job) }
} label: {
Label("Run", systemImage: "play.fill")
.font(OpenClawType.captionSemiBold)
}
.disabled(busy || !self.liveGatewayConnected)
@@ -105,6 +106,7 @@ extension AgentProTab {
Task { await self.setCronJob(job, enabled: !job.enabled) }
} label: {
Label(job.enabled ? "Pause" : "Enable", systemImage: job.enabled ? "pause.fill" : "checkmark")
.font(OpenClawType.captionSemiBold)
}
.disabled(busy || !self.liveGatewayConnected)
}
@@ -40,6 +40,7 @@ extension AgentProTab {
.refreshable {
await self.refreshOverview(force: true)
}
.font(OpenClawType.body)
.toolbar {
if let headerLeadingAction {
ToolbarItem(placement: .topBarLeading) {
@@ -70,6 +71,7 @@ extension AgentProTab {
self.skillsList
}
.padding(.vertical, 18)
.font(OpenClawType.body)
}
.refreshable {
await self.refreshOverview(force: true)
@@ -113,6 +115,7 @@ extension AgentProTab {
self.cronJobsList(limit: nil)
}
.padding(.vertical, 18)
.font(OpenClawType.body)
}
.refreshable {
await self.refreshOverview(force: true)
@@ -142,6 +145,7 @@ extension AgentProTab {
self.usageDailyList
}
.padding(.vertical, 18)
.font(OpenClawType.body)
}
.refreshable {
await self.refreshOverview(force: true)
@@ -21,18 +21,27 @@ extension AgentProTab {
}
HStack(spacing: 8) {
Button("Enable All") {
Button {
Task { await self.enableAllSkills() }
} label: {
Text("Enable All")
.font(OpenClawType.captionSemiBold)
}
.disabled(self.skillMutationBusy)
Button("Disable All", role: .destructive) {
Button(role: .destructive) {
Task { await self.disableAllSkills() }
} label: {
Text("Disable All")
.font(OpenClawType.captionSemiBold)
}
.disabled(self.skillMutationBusy)
Button("Reset") {
Button {
Task { await self.resetSkillPolicy() }
} label: {
Text("Reset")
.font(OpenClawType.captionSemiBold)
}
.disabled(self.skillMutationBusy || self.agentSkillFilter == nil)
}
@@ -77,7 +86,9 @@ extension AgentProTab {
}
Picker("Status", selection: self.$skillStatusFilter) {
ForEach(SkillStatusFilter.allCases) { filter in
Text(filter.title).tag(filter)
Text(filter.title)
.font(OpenClawType.captionSemiBold)
.tag(filter)
}
}
.pickerStyle(.segmented)
@@ -381,8 +392,11 @@ extension AgentProTab {
.navigationTitle("Skill")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Close") {
Button {
self.skillEditorSelection = nil
} label: {
Text("Close")
.font(OpenClawType.subheadSemiBold)
}
}
}
@@ -407,8 +421,11 @@ extension AgentProTab {
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Close") {
Button {
self.skillEditorSelection = nil
} label: {
Text("Close")
.font(OpenClawType.subheadSemiBold)
}
}
}
@@ -459,6 +476,7 @@ extension AgentProTab {
Task { await self.saveSkillAPIKey(skill) }
} label: {
Label("Save key", systemImage: "key")
.font(OpenClawType.captionSemiBold)
}
.buttonStyle(.borderedProminent)
.controlSize(.small)
@@ -523,6 +541,7 @@ extension AgentProTab {
Task { await self.installSkillRequirements(skill) }
} label: {
Label(install.label, systemImage: "wrench.and.screwdriver")
.font(OpenClawType.captionSemiBold)
}
.buttonStyle(.bordered)
.controlSize(.small)
@@ -48,6 +48,7 @@ struct IPadSidebarScreenChrome<Content: View>: View {
self.content
}
.padding(.vertical, self.isCompactHeight ? 10 : 18)
.font(OpenClawType.body)
}
.safeAreaPadding(.bottom, self.bottomScrollInset)
}
@@ -71,8 +71,11 @@ struct IPadSkillWorkshopScreen: View {
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Done") {
Button {
self.presentedProposalRoute = nil
} label: {
Text("Done")
.font(OpenClawType.subheadSemiBold)
}
}
}
@@ -118,6 +121,7 @@ struct IPadSkillWorkshopScreen: View {
Task { await self.loadProposals(force: true) }
} label: {
Label("Refresh", systemImage: "arrow.clockwise")
.font(OpenClawType.captionSemiBold)
}
.buttonStyle(.bordered)
.controlSize(.small)
@@ -177,6 +181,7 @@ struct IPadSkillWorkshopScreen: View {
Task { await self.loadProposals(force: true) }
} label: {
Label("Refresh", systemImage: "arrow.clockwise")
.font(OpenClawType.captionSemiBold)
.frame(maxWidth: .infinity)
}
.buttonStyle(.bordered)
@@ -226,12 +231,18 @@ struct IPadSkillWorkshopScreen: View {
.font(OpenClawType.captionSemiBold)
.foregroundStyle(.secondary)
Menu {
Button("Default agent") {
Button {
self.selectedAgentScopeID = ""
} label: {
Text("Default agent")
.font(OpenClawType.subhead)
}
ForEach(self.agentScopeOptions, id: \.id) { option in
Button(option.title) {
Button {
self.selectedAgentScopeID = option.id
} label: {
Text(option.title)
.font(OpenClawType.subhead)
}
}
} label: {
@@ -347,42 +358,60 @@ struct IPadSkillWorkshopScreen: View {
}
.buttonStyle(.plain)
.contextMenu {
Button("Inspect") {
Button {
self.selectProposal(
proposal,
opensSheet: true,
forceInspect: true)
} label: {
Text("Inspect")
.font(OpenClawType.subhead)
}
if proposal.status == "pending" {
Button("Apply") {
Button {
Task { await self.run(.apply, proposal: proposal) }
} label: {
Text("Apply")
.font(OpenClawType.subhead)
}
.disabled(!self.canApplyProposalMutations || self.busyAction != nil)
Button("Reject", role: .destructive) {
Button(role: .destructive) {
Task { await self.run(.reject, proposal: proposal) }
} label: {
Text("Reject")
.font(OpenClawType.subhead)
}
.disabled(!self.canApplyProposalMutations || self.busyAction != nil)
}
}
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
if proposal.status == "pending" {
Button("Apply") {
Button {
Task { await self.run(.apply, proposal: proposal) }
} label: {
Text("Apply")
.font(OpenClawType.subhead)
}
.tint(OpenClawBrand.ok)
.disabled(!self.canApplyProposalMutations || self.busyAction != nil)
Button("Reject", role: .destructive) {
Button(role: .destructive) {
Task { await self.run(.reject, proposal: proposal) }
} label: {
Text("Reject")
.font(OpenClawType.subhead)
}
.disabled(!self.canApplyProposalMutations || self.busyAction != nil)
}
}
.swipeActions(edge: .leading, allowsFullSwipe: true) {
Button("Inspect") {
Button {
self.selectProposal(
proposal,
opensSheet: true,
forceInspect: true)
} label: {
Text("Inspect")
.font(OpenClawType.subhead)
}
.tint(OpenClawBrand.accent)
}
@@ -482,6 +511,7 @@ struct IPadSkillWorkshopScreen: View {
Task { await self.run(.apply, proposal: proposal) }
} label: {
Label("Apply", systemImage: "checkmark.circle")
.font(OpenClawType.captionSemiBold)
.frame(maxWidth: self.isCompactWidth ? .infinity : nil)
}
.buttonStyle(.borderedProminent)
@@ -494,6 +524,7 @@ struct IPadSkillWorkshopScreen: View {
Task { await self.run(.reject, proposal: proposal) }
} label: {
Label("Reject", systemImage: "xmark.circle")
.font(OpenClawType.captionSemiBold)
.frame(maxWidth: self.isCompactWidth ? .infinity : nil)
}
.buttonStyle(.bordered)
@@ -506,6 +537,7 @@ struct IPadSkillWorkshopScreen: View {
Task { await self.inspect(proposalID: proposal.id, force: true) }
} label: {
Label("Inspect", systemImage: "doc.text.magnifyingglass")
.font(OpenClawType.captionSemiBold)
.frame(maxWidth: self.isCompactWidth ? .infinity : nil)
}
.buttonStyle(.bordered)
@@ -976,6 +1008,7 @@ private struct IPadSkillProposalKanbanCard: View {
if self.proposal.status == "pending" {
Button(action: self.apply) {
Image(systemName: "checkmark.circle")
.font(OpenClawType.captionSemiBold)
}
.accessibilityLabel("Apply Proposal")
.buttonStyle(.bordered)
@@ -984,6 +1017,7 @@ private struct IPadSkillProposalKanbanCard: View {
Button(role: .destructive, action: self.reject) {
Image(systemName: "xmark.circle")
.font(OpenClawType.captionSemiBold)
}
.accessibilityLabel("Reject Proposal")
.buttonStyle(.bordered)
@@ -993,6 +1027,7 @@ private struct IPadSkillProposalKanbanCard: View {
Button(action: self.inspect) {
Image(systemName: "doc.text.magnifyingglass")
.font(OpenClawType.captionSemiBold)
}
.accessibilityLabel("Inspect Proposal")
.buttonStyle(.bordered)
@@ -1006,12 +1041,21 @@ private struct IPadSkillProposalKanbanCard: View {
in: RoundedRectangle(cornerRadius: OpenClawRadius.xs, style: .continuous))
.contentShape(Rectangle())
.contextMenu {
Button("Inspect", action: self.inspect)
Button(action: self.inspect) {
Text("Inspect")
.font(OpenClawType.subhead)
}
if self.proposal.status == "pending" {
Button("Apply", action: self.apply)
.disabled(!self.canApplyProposalMutations || self.isBusy)
Button("Reject", role: .destructive, action: self.reject)
.disabled(!self.canApplyProposalMutations || self.isBusy)
Button(action: self.apply) {
Text("Apply")
.font(OpenClawType.subhead)
}
.disabled(!self.canApplyProposalMutations || self.isBusy)
Button(role: .destructive, action: self.reject) {
Text("Reject")
.font(OpenClawType.subhead)
}
.disabled(!self.canApplyProposalMutations || self.isBusy)
}
}
}
@@ -29,6 +29,7 @@ struct OpenClawDocsScreen: View {
self.linkCard
}
.padding(.vertical, 18)
.font(OpenClawType.body)
}
}
.navigationTitle("Docs")
@@ -39,6 +40,7 @@ struct OpenClawDocsScreen: View {
ToolbarItem(placement: .topBarTrailing) {
Button(action: gatewayAction) {
Image(systemName: "antenna.radiowaves.left.and.right")
.font(OpenClawType.subheadSemiBold)
}
.accessibilityLabel("Gateway settings")
}
@@ -47,9 +47,11 @@ struct ProSectionHeader: View {
Spacer()
if let actionTitle {
if let action {
Button(actionTitle, action: action)
.font(OpenClawType.footnoteMedium)
.foregroundStyle(OpenClawBrand.accent)
Button(action: action) {
Text(actionTitle)
.font(OpenClawType.footnoteMedium)
}
.foregroundStyle(OpenClawBrand.accent)
} else {
Text(actionTitle)
.font(OpenClawType.footnoteMedium)
@@ -134,19 +136,23 @@ private struct OpenClawGlassButtonModifier: ViewModifier {
if #available(iOS 26.0, *) {
if self.prominent {
content
.font(OpenClawType.subheadSemiBold)
.buttonStyle(.glassProminent)
.tint(self.tint ?? OpenClawBrand.accent)
} else {
content
.font(OpenClawType.subheadSemiBold)
.buttonStyle(.glass)
.tint(self.tint)
}
} else if self.prominent {
content
.font(OpenClawType.subheadSemiBold)
.buttonStyle(.borderedProminent)
.tint(self.tint ?? OpenClawBrand.accent)
} else {
content
.font(OpenClawType.subheadSemiBold)
.buttonStyle(.bordered)
.tint(self.tint)
}
@@ -362,14 +368,20 @@ struct OpenClawNoticeBanner: View {
OpenClawGlassControlGroup {
HStack(spacing: 10) {
if let primaryActionTitle, let onPrimaryAction {
Button(primaryActionTitle, action: onPrimaryAction)
.openClawGlassButton(prominent: true)
.controlSize(.small)
Button(action: onPrimaryAction) {
Text(primaryActionTitle)
.font(OpenClawType.captionSemiBold)
}
.openClawGlassButton(prominent: true)
.controlSize(.small)
}
if let secondaryActionTitle, let onSecondaryAction {
Button(secondaryActionTitle, action: onSecondaryAction)
.openClawGlassButton()
.controlSize(.small)
Button(action: onSecondaryAction) {
Text(secondaryActionTitle)
.font(OpenClawType.captionSemiBold)
}
.openClawGlassButton()
.controlSize(.small)
}
}
}
@@ -795,9 +807,11 @@ struct ProPanelHeader: View {
.accessibilityLabel(self.actionAccessibilityLabel ?? actionTitle ?? self.title)
.disabled(self.isActionDisabled)
} else if let actionTitle {
Button(actionTitle, action: action)
.font(OpenClawType.captionSemiBold)
.disabled(self.isActionDisabled)
Button(action: action) {
Text(actionTitle)
.font(OpenClawType.captionSemiBold)
}
.disabled(self.isActionDisabled)
}
}
}
@@ -830,10 +844,12 @@ struct ProStatusRow: View {
ProValuePill(value: value, color: self.color)
}
if let actionTitle, let action {
Button(actionTitle, action: action)
.font(OpenClawType.captionSemiBold)
.buttonStyle(.bordered)
.controlSize(.mini)
Button(action: action) {
Text(actionTitle)
.font(OpenClawType.captionSemiBold)
}
.buttonStyle(.bordered)
.controlSize(.mini)
}
}
}
@@ -22,6 +22,7 @@ struct SettingsChannelsDestination: View {
}
self.channelsCard
}
.font(OpenClawType.body)
.task(id: self.refreshID) {
await self.loadChannels(force: false)
}
@@ -380,6 +381,7 @@ private struct SettingsChannelRow: View {
private func accountRow(_ account: SettingsChannelAccount) -> some View {
HStack(spacing: 10) {
Image(systemName: account.running || account.connected ? "checkmark.circle.fill" : "circle")
.font(OpenClawType.captionSemiBold)
.foregroundStyle(account.color)
.frame(width: 28, height: 28)
VStack(alignment: .leading, spacing: 2) {
@@ -393,22 +395,32 @@ private struct SettingsChannelRow: View {
Spacer(minLength: 8)
Menu {
if account.running {
Button("Stop") {
Button {
self.stop(account.id)
} label: {
Text("Stop")
.font(OpenClawType.subhead)
}
} else {
Button("Start") {
Button {
self.start(account.id)
} label: {
Text("Start")
.font(OpenClawType.subhead)
}
.disabled(!account.configured || !account.enabled)
}
if account.linked {
Button("Logout", role: .destructive) {
Button(role: .destructive) {
self.logout(account.id)
} label: {
Text("Logout")
.font(OpenClawType.subhead)
}
}
} label: {
Image(systemName: self.actionMenuIcon(account))
.font(OpenClawType.captionSemiBold)
}
.buttonStyle(.bordered)
.controlSize(.mini)
+16 -3
View File
@@ -119,6 +119,7 @@ struct SettingsProTab: View {
self.gatewaySection
self.settingsListSection
}
.font(OpenClawType.body)
.listStyle(.insetGrouped)
.navigationTitle("Settings")
.navigationBarTitleDisplayMode(.large)
@@ -216,9 +217,15 @@ struct SettingsProTab: View {
.ignoresSafeArea()
.navigationTitle("Scan QR Code")
.navigationBarTitleDisplayMode(.inline)
.font(OpenClawType.body)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button("Cancel") { self.showQRScanner = false }
Button {
self.showQRScanner = false
} label: {
Text("Cancel")
.font(OpenClawType.subheadSemiBold)
}
}
}
}
@@ -294,17 +301,23 @@ struct HostedPushRelayDisclosureSheet: View {
.foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
.font(OpenClawType.body)
}
VStack(spacing: 10) {
Button {
self.dismiss()
self.onContinue()
} label: {
Text("Continue").frame(maxWidth: .infinity)
Text("Continue")
.font(OpenClawType.subheadSemiBold)
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
Button("Not Now", role: .cancel) {
Button(role: .cancel) {
self.dismiss()
} label: {
Text("Not Now")
.font(OpenClawType.subheadSemiBold)
}
.buttonStyle(.bordered)
.frame(maxWidth: .infinity)
@@ -241,6 +241,7 @@ extension SettingsProTab {
}
.padding(.top, 18)
.padding(.bottom, OpenClawProMetric.bottomScrollInset)
.font(OpenClawType.body)
}
}
.navigationTitle(self.title(for: route))
@@ -335,6 +336,7 @@ extension SettingsProTab {
self.openNotificationsRouteFromApprovals()
} label: {
Label("Open Notifications", systemImage: "bell.badge")
.font(OpenClawType.captionSemiBold)
.frame(maxWidth: .infinity)
}
.buttonStyle(.bordered)
@@ -369,6 +371,7 @@ extension SettingsProTab {
Task { await self.appModel.resolvePendingExecApprovalPrompt(decision: "allow-once") }
} label: {
Label("Allow", systemImage: "checkmark")
.font(OpenClawType.captionSemiBold)
}
.buttonStyle(.borderedProminent)
.disabled(self.appModel.pendingExecApprovalPromptResolving)
@@ -380,6 +383,7 @@ extension SettingsProTab {
}
} label: {
Label("Always", systemImage: "checkmark.shield")
.font(OpenClawType.captionSemiBold)
}
.buttonStyle(.bordered)
.disabled(self.appModel.pendingExecApprovalPromptResolving)
@@ -389,6 +393,7 @@ extension SettingsProTab {
Task { await self.appModel.resolvePendingExecApprovalPrompt(decision: "deny") }
} label: {
Label("Deny", systemImage: "xmark")
.font(OpenClawType.captionSemiBold)
}
.buttonStyle(.bordered)
.disabled(self.appModel.pendingExecApprovalPromptResolving)
@@ -530,6 +535,7 @@ extension SettingsProTab {
Label(
self.notificationActionText,
systemImage: self.notificationStatus.actionIcon)
.font(OpenClawType.captionSemiBold)
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
@@ -710,9 +716,15 @@ extension SettingsProTab {
}
Picker("Location", selection: self.$locationModeRaw) {
Text("Off").tag(OpenClawLocationMode.off.rawValue)
Text("While Using").tag(OpenClawLocationMode.whileUsing.rawValue)
Text("Always").tag(OpenClawLocationMode.always.rawValue)
Text("Off")
.font(OpenClawType.captionSemiBold)
.tag(OpenClawLocationMode.off.rawValue)
Text("While Using")
.font(OpenClawType.captionSemiBold)
.tag(OpenClawLocationMode.whileUsing.rawValue)
Text("Always")
.font(OpenClawType.captionSemiBold)
.tag(OpenClawLocationMode.always.rawValue)
}
.pickerStyle(.segmented)
.disabled(self.isChangingLocationMode)
@@ -793,12 +805,16 @@ extension SettingsProTab {
Text("Default Agent")
.font(OpenClawType.subheadSemiBold)
Picker("Agent", selection: self.$selectedAgentPickerId) {
Text("Default").tag("")
Text("Default")
.font(OpenClawType.subhead)
.tag("")
let defaultId = (self.appModel.gatewayDefaultAgentId ?? "")
.trimmingCharacters(in: .whitespacesAndNewlines)
ForEach(self.appModel.gatewayAgents.filter { $0.id != defaultId }, id: \.id) { agent in
let name = (agent.name ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
Text(name.isEmpty ? agent.id : name).tag(agent.id)
Text(name.isEmpty ? agent.id : name)
.font(OpenClawType.subhead)
.tag(agent.id)
}
}
Text("Used for new Chat and Talk sessions.")
@@ -817,6 +833,7 @@ extension SettingsProTab {
TextField("Paste setup code", text: self.$setupCode)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.font(OpenClawType.subhead)
.textFieldStyle(.roundedBorder)
HStack(spacing: 10) {
self.gatewayActionButton(
@@ -893,6 +910,7 @@ extension SettingsProTab {
ProgressView().controlSize(.small)
} else {
Text("Connect")
.font(OpenClawType.captionSemiBold)
}
}
.buttonStyle(.bordered)
@@ -907,9 +925,11 @@ extension SettingsProTab {
TextField("Host", text: self.$manualGatewayHost)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.font(OpenClawType.subhead)
.textFieldStyle(.roundedBorder)
TextField("Port", text: self.manualPortBinding)
.keyboardType(.numberPad)
.font(OpenClawType.subhead)
.textFieldStyle(.roundedBorder)
self.settingsButtonToggle("Use TLS", isOn: self.$manualGatewayTLS)
self.gatewayActionButton(
@@ -934,13 +954,16 @@ extension SettingsProTab {
SecureField("Gateway Auth Token", text: self.$gatewayToken)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.font(OpenClawType.subhead)
.textFieldStyle(.roundedBorder)
SecureField("Gateway Password", text: self.$gatewayPassword)
.font(OpenClawType.subhead)
.textFieldStyle(.roundedBorder)
Button(role: .destructive) {
self.showResetOnboardingAlert = true
} label: {
Label("Reset Onboarding", systemImage: "arrow.counterclockwise")
.font(OpenClawType.captionSemiBold)
.frame(maxWidth: .infinity)
}
.buttonStyle(.bordered)
@@ -966,7 +989,9 @@ extension SettingsProTab {
.disabled(self.appModel.isAppleReviewDemoModeEnabled)
Picker("Speech Language", selection: self.$talkSpeechLocale) {
ForEach(TalkSpeechLocale.supportedOptions()) { option in
Text(option.label).tag(option.id)
Text(option.label)
.font(OpenClawType.subhead)
.tag(option.id)
}
}
self.settingsToggle("Background Listening", isOn: self.$talkBackgroundEnabled)
@@ -999,14 +1024,20 @@ extension SettingsProTab {
VStack(alignment: .leading, spacing: 12) {
Picker("Provider", selection: self.talkProviderSelectionBinding) {
ForEach(TalkModeProviderSelection.allCases) { option in
Text(option.label).tag(option.rawValue)
Text(option.label)
.font(OpenClawType.subhead)
.tag(option.rawValue)
}
}
if self.shouldShowRealtimeVoicePicker {
Picker("Realtime Voice", selection: self.talkRealtimeVoiceSelectionBinding) {
Text("Gateway Default").tag("")
Text("Gateway Default")
.font(OpenClawType.subhead)
.tag("")
ForEach(TalkModeRealtimeVoiceSelection.voices, id: \.self) { voice in
Text(TalkModeRealtimeVoiceSelection.label(for: voice)).tag(voice)
Text(TalkModeRealtimeVoiceSelection.label(for: voice))
.font(OpenClawType.subhead)
.tag(voice)
}
}
}
@@ -1030,15 +1061,20 @@ extension SettingsProTab {
var shareSettingsCard: some View {
ProCard(radius: SettingsLayout.cardRadius) {
VStack(alignment: .leading, spacing: 12) {
Toggle("Show Talk Control", isOn: self.$talkButtonEnabled)
Toggle(isOn: self.$talkButtonEnabled) {
Text("Show Talk Control")
.font(OpenClawType.subhead)
}
TextField("Default Share Instruction", text: self.$defaultShareInstruction, axis: .vertical)
.lineLimit(2...5)
.textInputAutocapitalization(.sentences)
.font(OpenClawType.subhead)
.textFieldStyle(.roundedBorder)
Button {
Task { await self.appModel.runSharePipelineSelfTest() }
} label: {
Label("Run Share Self-Test", systemImage: "checkmark.seal")
.font(OpenClawType.captionSemiBold)
}
.buttonStyle(.bordered)
Text(self.appModel.lastShareEventText)
@@ -1089,10 +1125,13 @@ extension SettingsProTab {
isOn: Binding<Bool>,
onChange: ((Bool) -> Void)? = nil) -> some View
{
Toggle(title, isOn: isOn)
.onChange(of: isOn.wrappedValue) { _, enabled in
onChange?(enabled)
}
Toggle(isOn: isOn) {
Text(title)
.font(OpenClawType.subhead)
}
.onChange(of: isOn.wrappedValue) { _, enabled in
onChange?(enabled)
}
}
func settingsButtonToggle(
+4 -1
View File
@@ -60,8 +60,11 @@ struct TalkProTab: View {
.navigationTitle("Enable Talk")
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Not Now") {
Button {
self.showPermissionPrompt = false
} label: {
Text("Not Now")
.font(OpenClawType.subheadSemiBold)
}
}
}
@@ -60,9 +60,12 @@ struct TalkRuntimeIssueDetailsSheet: View {
.font(OpenClawType.monoFootnote)
.foregroundStyle(.secondary)
.textSelection(.enabled)
Button("Copy diagnostics") {
Button {
UIPasteboard.general.string = self.issue.technicalDetails
self.copyFeedback = "Copied diagnostics"
} label: {
Text("Copy diagnostics")
.font(OpenClawType.subheadSemiBold)
}
}
@@ -79,15 +82,21 @@ struct TalkRuntimeIssueDetailsSheet: View {
.toolbar {
ToolbarItem(placement: .topBarLeading) {
if let onOpenSettings {
Button("Open Settings") {
Button {
self.dismiss()
onOpenSettings()
} label: {
Text("Open Settings")
.font(OpenClawType.subheadSemiBold)
}
}
}
ToolbarItem(placement: .topBarTrailing) {
Button("Done") {
Button {
self.dismiss()
} label: {
Text("Done")
.font(OpenClawType.subheadSemiBold)
}
}
}
@@ -106,9 +106,12 @@ struct GatewayProblemDetailsSheet: View {
Text(verbatim: requestId)
.font(OpenClawType.mono)
.textSelection(.enabled)
Button("Copy request ID") {
Button {
UIPasteboard.general.string = requestId
self.copyFeedback = "Copied request ID"
} label: {
Text("Copy request ID")
.font(OpenClawType.subheadSemiBold)
}
}
}
@@ -118,9 +121,12 @@ struct GatewayProblemDetailsSheet: View {
Text(verbatim: actionCommand)
.font(OpenClawType.mono)
.textSelection(.enabled)
Button("Copy command") {
Button {
UIPasteboard.general.string = actionCommand
self.copyFeedback = "Copied command"
} label: {
Text("Copy command")
.font(OpenClawType.subheadSemiBold)
}
}
}
@@ -129,6 +135,7 @@ struct GatewayProblemDetailsSheet: View {
Section("Help") {
Link(destination: docsURL) {
Label("Open docs", systemImage: "book")
.font(OpenClawType.subheadSemiBold)
}
Text(verbatim: docsURL.absoluteString)
.font(OpenClawType.footnote)
@@ -159,15 +166,21 @@ struct GatewayProblemDetailsSheet: View {
.toolbar {
ToolbarItem(placement: .topBarLeading) {
if let primaryActionTitle, let onPrimaryAction {
Button(primaryActionTitle) {
Button {
self.dismiss()
onPrimaryAction()
} label: {
Text(primaryActionTitle)
.font(OpenClawType.subheadSemiBold)
}
}
}
ToolbarItem(placement: .topBarTrailing) {
Button("Done") {
Button {
self.dismiss()
} label: {
Text("Done")
.font(OpenClawType.subheadSemiBold)
}
}
}
@@ -87,6 +87,7 @@ struct GatewayQuickSetupSheet: View {
Spacer()
}
.font(OpenClawType.body)
.padding()
.navigationTitle("Quick Setup")
.navigationBarTitleDisplayMode(.inline)
@@ -97,6 +98,7 @@ struct GatewayQuickSetupSheet: View {
self.dismiss()
} label: {
Text("Close")
.font(OpenClawType.subheadSemiBold)
}
}
}
@@ -119,20 +121,23 @@ struct GatewayQuickSetupSheet: View {
}
private func fullRowToggle(_ title: LocalizedStringKey, 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)
Toggle(isOn: isOn) {
Text(title)
.font(OpenClawType.subhead)
}
.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? {
@@ -14,7 +14,7 @@ struct PrivacyAccessSectionView: View {
@Environment(\.scenePhase) private var scenePhase
var body: some View {
DisclosureGroup("Privacy & Access") {
DisclosureGroup {
self.permissionRow(
title: "Contacts",
icon: "person.crop.circle",
@@ -54,7 +54,11 @@ struct PrivacyAccessSectionView: View {
detail: "List, add, and complete reminders.",
actionTitle: self.remindersActionTitle,
action: self.handleRemindersAction)
} label: {
Text("Privacy & Access")
.font(OpenClawType.subheadSemiBold)
}
.font(OpenClawType.body)
.onAppear { self.refreshAll() }
.onChange(of: self.scenePhase) { _, phase in
if phase == .active {
@@ -74,6 +78,7 @@ struct PrivacyAccessSectionView: View {
VStack(alignment: .leading, spacing: 6) {
HStack {
Label(title, systemImage: icon)
.font(OpenClawType.subheadSemiBold)
Spacer()
OpenClawStatusBadge(label: status, tone: self.statusTone(for: status))
.accessibilityIdentifier("privacy-access-\(title)-status")
@@ -82,10 +87,12 @@ struct PrivacyAccessSectionView: View {
.font(OpenClawType.footnote)
.foregroundStyle(.secondary)
if let actionTitle, let action {
Button(actionTitle, action: action)
.font(OpenClawType.footnote)
.buttonStyle(.bordered)
.accessibilityIdentifier("privacy-access-\(title)-action")
Button(action: action) {
Text(actionTitle)
.font(OpenClawType.footnoteSemiBold)
}
.buttonStyle(.bordered)
.accessibilityIdentifier("privacy-access-\(title)-action")
}
}
.padding(.vertical, 2)
@@ -89,6 +89,63 @@ struct OpenClawTypographyTests {
#expect(!watchSource.contains(".font(.title"))
}
@Test func `listed iOS app surfaces enforce branded control typography`() throws {
let proComponents = try String(
contentsOf: Self.sourceURL("Design/OpenClawProComponents.swift"),
encoding: .utf8)
let quickSetup = try String(contentsOf: Self.sourceURL("Gateway/GatewayQuickSetupSheet.swift"), encoding: .utf8)
let settingsSections = try String(
contentsOf: Self.sourceURL("Design/SettingsProTabSections.swift"),
encoding: .utf8)
let privacyAccess = try String(
contentsOf: Self.sourceURL("Settings/PrivacyAccessSectionView.swift"),
encoding: .utf8)
let skillWorkshop = try String(
contentsOf: Self.sourceURL("Design/IPadSkillWorkshopScreen.swift"),
encoding: .utf8)
let agentDestinations = try String(
contentsOf: Self.sourceURL("Design/AgentProTab+Destinations.swift"),
encoding: .utf8)
let dreaming = try String(
contentsOf: Self.sourceURL("Design/AgentProDreamingDestination.swift"),
encoding: .utf8)
let instances = try String(contentsOf: Self.sourceURL("Design/AgentProNodesDestination.swift"), encoding: .utf8)
let channels = try String(
contentsOf: Self.sourceURL("Design/SettingsChannelsDestination.swift"),
encoding: .utf8)
let docs = try String(contentsOf: Self.sourceURL("Design/OpenClawDocsScreen.swift"), encoding: .utf8)
#expect(proComponents.contains(".font(OpenClawType.subheadSemiBold)"))
#expect(proComponents.contains("Text(primaryActionTitle)"))
#expect(proComponents.contains("Text(secondaryActionTitle)"))
#expect(!quickSetup.contains("Button(\"Close\")"))
#expect(quickSetup.contains("Text(\"Close\")"))
#expect(quickSetup.contains(".font(OpenClawType.subheadSemiBold)"))
#expect(settingsSections.contains(".font(OpenClawType.body)"))
#expect(settingsSections.contains("Toggle(isOn: self.$talkButtonEnabled)"))
#expect(settingsSections.contains("Text(\"Show Talk Control\")"))
#expect(settingsSections.contains("TextField(\"Default Share Instruction\""))
#expect(settingsSections.contains(".font(OpenClawType.subhead)"))
#expect(!privacyAccess.contains("DisclosureGroup(\"Privacy & Access\")"))
#expect(privacyAccess.contains("Text(\"Privacy & Access\")"))
#expect(privacyAccess.contains("Text(actionTitle)"))
#expect(privacyAccess.contains(".font(OpenClawType.footnoteSemiBold)"))
#expect(!skillWorkshop.contains("Button(\"Done\")"))
#expect(skillWorkshop.contains("Label(\"Refresh\", systemImage: \"arrow.clockwise\")"))
#expect(skillWorkshop.contains("Text(\"Default agent\")"))
#expect(skillWorkshop.contains("Text(\"Inspect\")"))
#expect(skillWorkshop.contains("Text(\"Apply\")"))
#expect(skillWorkshop.contains("Text(\"Reject\")"))
for source in [agentDestinations, dreaming, instances, channels, docs] {
#expect(source.contains(".font(OpenClawType.body)"))
}
}
private static let bundledFontFiles = [
"RedHatDisplay[wght].ttf",
"Inter[opsz,wght].ttf",
@@ -118,6 +175,10 @@ struct OpenClawTypographyTests {
self.iosRootURL().appendingPathComponent("WatchApp/Sources/WatchInboxView.swift")
}
private static func sourceURL(_ relativePath: String) -> URL {
self.iosRootURL().appendingPathComponent("Sources/\(relativePath)")
}
private static func iosRootURL() -> URL {
URL(fileURLWithPath: #filePath)
.deletingLastPathComponent()