diff --git a/apps/ios/Sources/Design/AgentProDreamingDestination.swift b/apps/ios/Sources/Design/AgentProDreamingDestination.swift index 729bde3a2ea1..8271ac0b0168 100644 --- a/apps/ios/Sources/Design/AgentProDreamingDestination.swift +++ b/apps/ios/Sources/Design/AgentProDreamingDestination.swift @@ -122,9 +122,9 @@ struct AgentProDreamingDestination: View { ProIconBadge(systemName: icon, color: color) VStack(alignment: .leading, spacing: 3) { Text(title) - .font(.headline) + .font(OpenClawType.headline) Text(detail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) } Spacer(minLength: 8) @@ -139,7 +139,7 @@ struct AgentProDreamingDestination: View { VStack(alignment: .leading, spacing: 12) { HStack { Text("Memory State") - .font(.headline) + .font(OpenClawType.headline) Spacer() ProValuePill(value: self.dreamingValue, color: self.dreamingColor) } @@ -156,7 +156,7 @@ struct AgentProDreamingDestination: View { } if let storeError = self.normalized(self.overview?.dreaming?.storeError) { Text(storeError) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(OpenClawBrand.warn) } } @@ -170,9 +170,9 @@ struct AgentProDreamingDestination: View { HStack { VStack(alignment: .leading, spacing: 3) { Text("Maintenance") - .font(.headline) + .font(OpenClawType.headline) Text("Refresh reads live state. Maintenance actions update the gateway diary/artifacts.") - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(2) } @@ -203,7 +203,7 @@ struct AgentProDreamingDestination: View { if let dreamActionStatusText { Text(dreamActionStatusText) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(.secondary) .lineLimit(3) } @@ -225,10 +225,10 @@ struct AgentProDreamingDestination: View { ProIconBadge(systemName: "book.pages", color: OpenClawBrand.accent) VStack(alignment: .leading, spacing: 2) { Text(diary.path) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(1) Text(self.dreamDiaryUpdatedLabel(diary)) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) } Spacer(minLength: 8) @@ -287,7 +287,7 @@ struct AgentProDreamingDestination: View { .lineLimit(1) .minimumScaleFactor(0.75) } - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(.primary) .padding(.horizontal, 10) .frame(height: 34) @@ -300,22 +300,24 @@ struct AgentProDreamingDestination: View { VStack(alignment: .leading, spacing: 8) { HStack(alignment: .firstTextBaseline) { Text(day.title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(1) Spacer(minLength: 8) Text("\(day.entryCount) \(day.entryCount == 1 ? "entry" : "entries")") - .font(.caption2.weight(.semibold)) + .font(OpenClawType.caption2SemiBold) .foregroundStyle(OpenClawBrand.accent) } Text(day.body) - .font(.caption.monospaced()) + .font(OpenClawType.monoSmall) .foregroundStyle(.primary) .lineLimit(120) .textSelection(.enabled) .frame(maxWidth: .infinity, alignment: .leading) } .padding(10) - .background(Color.primary.opacity(0.045), in: RoundedRectangle(cornerRadius: 10, style: .continuous)) + .background( + Color.primary.opacity(0.045), + in: RoundedRectangle(cornerRadius: OpenClawRadius.sm, style: .continuous)) } private func selectedDreamDiaryDay(from days: [DreamDiaryDay]) -> DreamDiaryDay? { @@ -362,21 +364,21 @@ struct AgentProDreamingDestination: View { ProIconBadge(systemName: "text.page", color: OpenClawBrand.accent) VStack(alignment: .leading, spacing: 4) { Text(self.dreamingEntryTitle(entry)) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(1) Text(entry.snippet) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(4) .textSelection(.enabled) Text(self.dreamingEntryDetail(entry)) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(.secondary) .lineLimit(1) } Spacer(minLength: 8) Text("\(entry.totalSignalCount)") - .font(.caption2.weight(.semibold)) + .font(OpenClawType.caption2SemiBold) .foregroundStyle(OpenClawBrand.accent) .lineLimit(1) } @@ -428,21 +430,21 @@ struct AgentProDreamingDestination: View { color: phase.status.enabled == false ? .secondary : OpenClawBrand.accent) VStack(alignment: .leading, spacing: 4) { Text(phase.title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) Text(self.dreamingPhaseDetail(phase.status)) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(2) if let cron = self.normalized(phase.status.cron) { Text(cron) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(.secondary) .lineLimit(1) } } Spacer(minLength: 8) Text(self.dreamingPhaseState(phase.status)) - .font(.caption2.weight(.semibold)) + .font(OpenClawType.caption2SemiBold) .foregroundStyle(phase.status.managedCronPresent == true ? OpenClawBrand.accent : .secondary) .lineLimit(1) } @@ -455,9 +457,9 @@ struct AgentProDreamingDestination: View { ProIconBadge(systemName: icon, color: .secondary) VStack(alignment: .leading, spacing: 3) { Text(title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) Text(detail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(2) } @@ -468,16 +470,18 @@ struct AgentProDreamingDestination: View { private func detailMetric(label: String, value: String) -> some View { VStack(alignment: .leading, spacing: 3) { Text(label) - .font(.caption2.weight(.medium)) + .font(OpenClawType.caption2Medium) .foregroundStyle(.secondary) Text(value) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(1) .minimumScaleFactor(0.8) } .frame(maxWidth: .infinity, alignment: .leading) .padding(10) - .background(Color.primary.opacity(0.055), in: RoundedRectangle(cornerRadius: 10, style: .continuous)) + .background( + Color.primary.opacity(0.055), + in: RoundedRectangle(cornerRadius: OpenClawRadius.sm, style: .continuous)) } private func dreamingEntryTitle(_ entry: DreamingEntryLite) -> String { diff --git a/apps/ios/Sources/Design/AgentProNodesDestination.swift b/apps/ios/Sources/Design/AgentProNodesDestination.swift index 1d8c59819f83..ab5c84a9c7b7 100644 --- a/apps/ios/Sources/Design/AgentProNodesDestination.swift +++ b/apps/ios/Sources/Design/AgentProNodesDestination.swift @@ -56,9 +56,9 @@ struct AgentProNodesDestination: View { ProIconBadge(systemName: "display", color: self.instancesColor) VStack(alignment: .leading, spacing: 3) { Text("Instances") - .font(.headline) + .font(OpenClawType.headline) Text(self.instancesDetail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) } Spacer(minLength: 8) @@ -73,7 +73,7 @@ struct AgentProNodesDestination: View { VStack(alignment: .leading, spacing: 12) { HStack { Text("Presence") - .font(.headline) + .font(OpenClawType.headline) Spacer() ProValuePill(value: self.instancesValue, color: self.instancesColor) } @@ -134,27 +134,27 @@ struct AgentProNodesDestination: View { ProIconBadge(systemName: Self.presenceIcon(entry), color: Self.presenceColor(entry)) VStack(alignment: .leading, spacing: 4) { Text(Self.presenceLabel(entry) ?? "Instance") - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(1) Text(Self.presenceDetail(entry)) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(2) if let meta = Self.presenceMeta(entry) { Text(meta) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(.secondary) .lineLimit(1) } } Spacer(minLength: 8) Text(Self.presenceState(entry)) - .font(.caption2.weight(.semibold)) + .font(OpenClawType.caption2SemiBold) .foregroundStyle(Self.presenceColor(entry)) .lineLimit(1) if showsChevron { Image(systemName: "chevron.right") - .font(.caption2.weight(.bold)) + .font(OpenClawType.caption2Bold) .foregroundStyle(.secondary) .padding(.top, 2) } @@ -173,9 +173,9 @@ struct AgentProNodesDestination: View { ProIconBadge(systemName: Self.presenceIcon(entry), color: Self.presenceColor(entry)) VStack(alignment: .leading, spacing: 3) { Text(Self.presenceLabel(entry) ?? "Instance") - .font(.headline) + .font(OpenClawType.headline) Text(Self.presenceDetail(entry)) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) } Spacer(minLength: 8) @@ -233,7 +233,7 @@ struct AgentProNodesDestination: View { .disabled(normalized == "n/a") .accessibilityLabel("Copy \(title)") } - .font(.subheadline) + .font(OpenClawType.subhead) .padding(.vertical, 10) } @@ -243,14 +243,14 @@ struct AgentProNodesDestination: View { ProCard { if values.isEmpty { Text("None reported.") - .font(.subheadline) + .font(OpenClawType.subhead) .foregroundStyle(.secondary) .frame(maxWidth: .infinity, alignment: .leading) } else { VStack(alignment: .leading, spacing: 8) { ForEach(values, id: \.self) { value in Text(value) - .font(.caption.monospaced()) + .font(OpenClawType.monoSmall) .textSelection(.enabled) .frame(maxWidth: .infinity, alignment: .leading) } @@ -264,16 +264,18 @@ struct AgentProNodesDestination: View { private func detailMetric(label: String, value: String) -> some View { VStack(alignment: .leading, spacing: 3) { Text(label) - .font(.caption2.weight(.medium)) + .font(OpenClawType.caption2Medium) .foregroundStyle(.secondary) Text(value) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(1) .minimumScaleFactor(0.8) } .frame(maxWidth: .infinity, alignment: .leading) .padding(10) - .background(Color.primary.opacity(0.055), in: RoundedRectangle(cornerRadius: 10, style: .continuous)) + .background( + Color.primary.opacity(0.055), + in: RoundedRectangle(cornerRadius: OpenClawRadius.sm, style: .continuous)) } private func emptyRow(icon: String, title: String, detail: String) -> some View { @@ -281,9 +283,9 @@ struct AgentProNodesDestination: View { ProIconBadge(systemName: icon, color: .secondary) VStack(alignment: .leading, spacing: 3) { Text(title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) Text(detail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(2) } diff --git a/apps/ios/Sources/Design/AgentProTab+Cron.swift b/apps/ios/Sources/Design/AgentProTab+Cron.swift index f0a85c31b5f3..587f05cca7de 100644 --- a/apps/ios/Sources/Design/AgentProTab+Cron.swift +++ b/apps/ios/Sources/Design/AgentProTab+Cron.swift @@ -8,7 +8,7 @@ extension AgentProTab { VStack(alignment: .leading, spacing: 12) { HStack { Text("Scheduler") - .font(.headline) + .font(OpenClawType.headline) Spacer() ProValuePill( value: self.overview?.cronStatus?.enabled == true ? "on" : "off", @@ -23,7 +23,7 @@ extension AgentProTab { } if let cronActionStatusText { Text(cronActionStatusText) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(.secondary) .lineLimit(2) } @@ -83,14 +83,14 @@ extension AgentProTab { color: job.enabled ? OpenClawBrand.accent : .secondary) VStack(alignment: .leading, spacing: 4) { Text(job.name) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(1) Text(self.cronJobDetail(job)) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(2) Text(self.cronScheduleSummary(job)) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(.secondary) .lineLimit(1) HStack(spacing: 8) { @@ -118,7 +118,7 @@ extension AgentProTab { .controlSize(.small) } else { Text(self.cronJobState(job)) - .font(.caption2.weight(.semibold)) + .font(OpenClawType.caption2SemiBold) .foregroundStyle(job.enabled ? OpenClawBrand.accent : .secondary) .lineLimit(1) } diff --git a/apps/ios/Sources/Design/AgentProTab+Destinations.swift b/apps/ios/Sources/Design/AgentProTab+Destinations.swift index 9f66be2f6e16..3772a5f3fd15 100644 --- a/apps/ios/Sources/Design/AgentProTab+Destinations.swift +++ b/apps/ios/Sources/Design/AgentProTab+Destinations.swift @@ -199,9 +199,9 @@ extension AgentProTab { ProIconBadge(systemName: icon, color: color) VStack(alignment: .leading, spacing: 3) { Text(title) - .font(.headline) + .font(OpenClawType.headline) Text(detail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) } Spacer(minLength: 8) diff --git a/apps/ios/Sources/Design/AgentProTab+DetailComponents.swift b/apps/ios/Sources/Design/AgentProTab+DetailComponents.swift index 42c002fe16d4..89366d8ab3ca 100644 --- a/apps/ios/Sources/Design/AgentProTab+DetailComponents.swift +++ b/apps/ios/Sources/Design/AgentProTab+DetailComponents.swift @@ -5,16 +5,18 @@ extension AgentProTab { func detailMetric(label: String, value: String) -> some View { VStack(alignment: .leading, spacing: 3) { Text(label) - .font(.caption2.weight(.medium)) + .font(OpenClawType.caption2Medium) .foregroundStyle(.secondary) Text(value) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(1) .minimumScaleFactor(0.8) } .frame(maxWidth: .infinity, alignment: .leading) .padding(10) - .background(Color.primary.opacity(0.055), in: RoundedRectangle(cornerRadius: 10, style: .continuous)) + .background( + Color.primary.opacity(0.055), + in: RoundedRectangle(cornerRadius: OpenClawRadius.sm, style: .continuous)) } func emptyDetailRow(icon: String, title: String, detail: String) -> some View { @@ -22,9 +24,9 @@ extension AgentProTab { ProIconBadge(systemName: icon, color: .secondary) VStack(alignment: .leading, spacing: 3) { Text(title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) Text(detail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(2) } diff --git a/apps/ios/Sources/Design/AgentProTab+Overview.swift b/apps/ios/Sources/Design/AgentProTab+Overview.swift index e82f1b14cbe4..a5fe1810716f 100644 --- a/apps/ios/Sources/Design/AgentProTab+Overview.swift +++ b/apps/ios/Sources/Design/AgentProTab+Overview.swift @@ -36,7 +36,7 @@ extension AgentProTab { TextField("Search agents", text: self.$agentSearchText) .textInputAutocapitalization(.never) .autocorrectionDisabled() - .font(.subheadline) + .font(OpenClawType.subhead) .textFieldStyle(.roundedBorder) .frame(height: 38) .transition(.move(edge: .top).combined(with: .opacity)) @@ -77,7 +77,7 @@ extension AgentProTab { } } label: { Image(systemName: "xmark.circle.fill") - .font(.title3) + .font(OpenClawType.title3) .foregroundStyle(.secondary) .frame(width: 44, height: 44) .contentShape(Circle()) @@ -185,7 +185,7 @@ extension AgentProTab { if let overviewErrorText { Text(overviewErrorText) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(OpenClawBrand.warn) .padding(.horizontal, OpenClawProMetric.pagePadding) } @@ -245,9 +245,9 @@ extension AgentProTab { ProIconBadge(systemName: "person.2.slash", color: .secondary) VStack(alignment: .leading, spacing: 3) { Text(self.emptyAgentsTitle) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) Text(self.emptyAgentsDetail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) } Spacer() @@ -266,12 +266,12 @@ extension AgentProTab { VStack(alignment: .leading, spacing: 3) { Text(self.agentName(for: agent)) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .foregroundStyle(.primary) .lineLimit(1) Text(self.agentDetail(for: agent)) - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(.secondary) .lineLimit(1) } @@ -281,7 +281,7 @@ extension AgentProTab { if isActive { Image(systemName: "checkmark") - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .foregroundStyle(OpenClawBrand.accent) .frame(width: 24, height: 44) .accessibilityHidden(true) @@ -302,7 +302,7 @@ extension AgentProTab { { Button(action: action) { Image(systemName: systemName) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .frame(width: AgentLayout.filterHeight, height: AgentLayout.filterHeight) } .buttonBorderShape(.circle) @@ -342,20 +342,20 @@ extension AgentProTab { ProIconBadge(systemName: icon, color: color) VStack(alignment: .leading, spacing: 3) { Text(title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) Text(detail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(1) } Spacer(minLength: 8) Text(value) - .font(.caption2.weight(.semibold)) + .font(OpenClawType.caption2SemiBold) .foregroundStyle(color) .lineLimit(1) if showsChevron { Image(systemName: "chevron.right") - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(.secondary) } } @@ -410,15 +410,15 @@ extension AgentProTab { ProValuePill(value: value, color: color) if showsChevron { Image(systemName: "chevron.right") - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(.secondary) } } VStack(alignment: .leading, spacing: 2) { Text(title) - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) Text(detail) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(.secondary) .lineLimit(2) .multilineTextAlignment(.leading) @@ -434,11 +434,11 @@ extension AgentProTab { ProIconBadge(systemName: "clock.badge.questionmark", color: .secondary) VStack(alignment: .leading, spacing: 3) { Text(self.gatewayConnected ? "No scheduled jobs" : "Cron unavailable") - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) Text(self.gatewayConnected ? "The gateway has no visible cron jobs." : "Connect a gateway to load scheduled work.") - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) } Spacer() @@ -452,16 +452,16 @@ extension AgentProTab { color: job.enabled ? OpenClawBrand.accent : .secondary) VStack(alignment: .leading, spacing: 3) { Text(job.name) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(1) Text(self.cronJobDetail(job)) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(1) } Spacer(minLength: 8) Text(self.cronJobState(job)) - .font(.caption2.weight(.semibold)) + .font(OpenClawType.caption2SemiBold) .foregroundStyle(job.enabled ? OpenClawBrand.accent : .secondary) .lineLimit(1) } diff --git a/apps/ios/Sources/Design/AgentProTab+Skills.swift b/apps/ios/Sources/Design/AgentProTab+Skills.swift index f9068ac38969..828cb2828785 100644 --- a/apps/ios/Sources/Design/AgentProTab+Skills.swift +++ b/apps/ios/Sources/Design/AgentProTab+Skills.swift @@ -9,9 +9,9 @@ extension AgentProTab { HStack(alignment: .firstTextBaseline) { VStack(alignment: .leading, spacing: 3) { Text(self.activeAgentName) - .font(.headline) + .font(OpenClawType.headline) Text(self.skillPolicySummary) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) } Spacer(minLength: 8) @@ -41,12 +41,12 @@ extension AgentProTab { if let skillMutationStatusText { Text(skillMutationStatusText) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(OpenClawBrand.accent) } if let skillMutationErrorText { Text(skillMutationErrorText) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(OpenClawBrand.warn) } } @@ -59,12 +59,12 @@ extension AgentProTab { VStack(alignment: .leading, spacing: 10) { HStack(spacing: 10) { Image(systemName: "magnifyingglass") - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(.secondary) TextField("Search skills", text: self.$skillFilter) .textInputAutocapitalization(.never) .autocorrectionDisabled() - .font(.subheadline) + .font(OpenClawType.subhead) if !self.skillFilter.isEmpty { Button { self.skillFilter = "" @@ -94,9 +94,9 @@ extension AgentProTab { ProIconBadge(systemName: "square.and.arrow.down", color: OpenClawBrand.accent) VStack(alignment: .leading, spacing: 2) { Text("Install Skills") - .font(.headline) + .font(OpenClawType.headline) Text("Search ClawHub and install into this workspace.") - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) } Spacer(minLength: 8) @@ -114,7 +114,7 @@ extension AgentProTab { TextField("Search ClawHub", text: self.$clawHubQuery) .textInputAutocapitalization(.never) .autocorrectionDisabled() - .font(.subheadline) + .font(OpenClawType.subhead) .submitLabel(.search) .onSubmit { Task { await self.searchClawHubSkills() } @@ -126,7 +126,7 @@ extension AgentProTab { } if let clawHubErrorText { Text(clawHubErrorText) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(OpenClawBrand.warn) } if !self.clawHubResults.isEmpty { @@ -151,10 +151,10 @@ extension AgentProTab { ProIconBadge(systemName: "sparkles", color: OpenClawBrand.accent) VStack(alignment: .leading, spacing: 3) { Text(result.displayName) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(1) Text(result.summary ?? result.slug) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(2) } @@ -277,21 +277,21 @@ extension AgentProTab { ProIconBadge(systemName: self.isSkillAllowed(skill) ? "checkmark.circle" : "nosign", color: status.color) VStack(alignment: .leading, spacing: 4) { Text(skill.displayName) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(1) Text(self.normalized(skill.description) ?? self.normalized(skill.source) ?? "Workspace skill") - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(2) if let missing = skill.missingSummary { Text("Missing: \(missing)") - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(OpenClawBrand.warn) .lineLimit(1) } if let install = skill.installSummary { Text("Setup: \(install)") - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(.secondary) .lineLimit(1) } @@ -321,7 +321,7 @@ extension AgentProTab { .accessibilityLabel("Edit \(skill.displayName)") } Text(busy ? "saving" : status.text) - .font(.caption2.weight(.semibold)) + .font(OpenClawType.caption2SemiBold) .foregroundStyle(status.color) .lineLimit(1) } @@ -424,9 +424,9 @@ extension AgentProTab { color: status.color) VStack(alignment: .leading, spacing: 3) { Text(skill.displayName) - .font(.headline) + .font(OpenClawType.headline) Text(self.normalized(skill.description) ?? self.normalized(skill.source) ?? "Workspace skill") - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(3) } @@ -451,7 +451,7 @@ extension AgentProTab { if let primaryEnv = skill.primaryEnv, !primaryEnv.isEmpty { VStack(alignment: .leading, spacing: 8) { Text("API key") - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) SecureField(primaryEnv, text: self.skillAPIKeyBinding(for: skill)) .textInputAutocapitalization(.never) .autocorrectionDisabled() @@ -465,14 +465,14 @@ extension AgentProTab { .disabled(self.isSkillConfigBusy(skill)) if let homepage = skill.homepageURL { Link("Get key", destination: homepage) - .font(.caption) + .font(OpenClawType.caption) } } } if let message = self.skillConfigMessages[skill.effectiveSkillKey] { Text(message.text) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(message.kind == .success ? OpenClawBrand.accent : OpenClawBrand.warn) } } @@ -494,7 +494,7 @@ extension AgentProTab { HStack { Text(title) Spacer(minLength: 8) - self.skillEditorSwitchIndicator(isOn: isOn) + OpenClawToggleIndicator(isOn: isOn) } .contentShape(Rectangle()) } @@ -504,31 +504,18 @@ extension AgentProTab { .accessibilityValue(isOn ? "On" : "Off") } - func skillEditorSwitchIndicator(isOn: Bool) -> some View { - Capsule() - .fill(isOn ? OpenClawBrand.accent : Color.secondary.opacity(0.35)) - .frame(width: 52, height: 32) - .overlay(alignment: isOn ? .trailing : .leading) { - Circle() - .fill(Color.white) - .frame(width: 28, height: 28) - .padding(2) - .shadow(color: Color.black.opacity(0.14), radius: 1, x: 0, y: 1) - } - } - func skillEditorSetup(_ skill: SkillStatusEntryLite) -> some View { ProCard(radius: AgentLayout.cardRadius) { VStack(alignment: .leading, spacing: 10) { Text("Setup") - .font(.headline) + .font(OpenClawType.headline) if let missing = skill.missingSummary { Text("Missing: \(missing)") - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(OpenClawBrand.warn) } else { Text("No missing requirements reported.") - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) } if let install = skill.install?.first { @@ -553,7 +540,7 @@ extension AgentProTab { self.detailMetric(label: "Source", value: self.normalized(skill.source) ?? "unknown") if let filePath = self.normalized(skill.filePath) { Text(filePath) - .font(.caption2.monospaced()) + .font(OpenClawType.monoSmall) .foregroundStyle(.secondary) .textSelection(.enabled) } diff --git a/apps/ios/Sources/Design/AgentProTab+Usage.swift b/apps/ios/Sources/Design/AgentProTab+Usage.swift index e047ba4fb613..36e826e2c230 100644 --- a/apps/ios/Sources/Design/AgentProTab+Usage.swift +++ b/apps/ios/Sources/Design/AgentProTab+Usage.swift @@ -8,7 +8,7 @@ extension AgentProTab { VStack(alignment: .leading, spacing: 12) { HStack { Text("Totals") - .font(.headline) + .font(OpenClawType.headline) Spacer() ProValuePill( value: "\(self.overview?.usage?.days ?? 31)d", @@ -67,14 +67,14 @@ extension AgentProTab { ProIconBadge(systemName: "calendar", color: OpenClawBrand.accent) VStack(alignment: .leading, spacing: 3) { Text(day.date) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) Text("\(Self.compactNumber(day.totalTokens ?? 0)) tokens") - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) } Spacer(minLength: 8) Text(Self.currency(day.totalCost ?? 0)) - .font(.caption2.weight(.semibold)) + .font(OpenClawType.caption2SemiBold) .foregroundStyle(OpenClawBrand.accent) } .padding(.vertical, 10) diff --git a/apps/ios/Sources/Design/ChatProTab.swift b/apps/ios/Sources/Design/ChatProTab.swift index 7b6fb57a3035..61fbe4ce855c 100644 --- a/apps/ios/Sources/Design/ChatProTab.swift +++ b/apps/ios/Sources/Design/ChatProTab.swift @@ -79,7 +79,6 @@ struct ChatProTab: View { composerChrome: .clean, isComposerEnabled: self.gatewayConnected, messagePlaceholder: self.messagePlaceholder, - emptyAssistantIntro: "What would you like to work on?", talkControl: self.talkControl) .id(ObjectIdentifier(viewModel)) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) @@ -87,9 +86,9 @@ struct ChatProTab: View { ProCard { VStack(alignment: .leading, spacing: 8) { Text("Chat is preparing") - .font(.headline) + .font(OpenClawType.headline) Text("The operator session will attach when the gateway is ready.") - .font(.subheadline) + .font(OpenClawType.subhead) .foregroundStyle(.secondary) } } @@ -108,8 +107,8 @@ struct ChatProTab: View { OpenClawAdaptiveHeaderRow( title: self.headerDisplayTitle, subtitle: self.headerDisplaySubtitle, - titleFont: .headline.weight(.semibold), - subtitleFont: .caption, + titleFont: OpenClawType.headline, + subtitleFont: OpenClawType.caption, subtitleLineLimit: 1) { HStack(spacing: 11) { @@ -119,25 +118,26 @@ struct ChatProTab: View { self.headerIdentityBadge } } accessory: { - self.connectionStatusButton + self.connectionPillButton } .padding(.horizontal, OpenClawProMetric.pagePadding) - .padding(.bottom, 2) + .padding(.bottom, 4) } @ViewBuilder private var headerIdentityBadge: some View { if self.showsAgentBadge { Text(self.agentBadge) - .font(.system(size: self.agentBadge.count > 2 ? 11 : 14, weight: .bold, design: .rounded)) + .font(.system(size: self.agentBadge.count > 2 ? 13 : 16, weight: .bold, design: .rounded)) .foregroundStyle(.white) .minimumScaleFactor(0.6) .lineLimit(1) - .frame(width: 32, height: 32) + .frame(width: 38, height: 38) .background( Circle() - .fill(OpenClawBrand.accent)) + .fill(OpenClawBrand.accent.gradient)) .overlay(Circle().strokeBorder(.white.opacity(0.18), lineWidth: 1)) + .shadow(color: OpenClawBrand.accent.opacity(0.14), radius: 5, y: 2) } else { ProIconBadge(systemName: "bubble.left", color: OpenClawBrand.accent) } @@ -196,44 +196,29 @@ struct ChatProTab: View { } @ViewBuilder - private var connectionStatusButton: some View { + private var connectionPillButton: some View { if let openSettings { Button(action: openSettings) { - self.connectionStatusIcon + self.connectionPill } - .buttonStyle(.plain) - .contentShape(Circle()) - .accessibilityLabel(self.gatewayAccessibilityLabel) + .buttonBorderShape(.capsule) + .openClawGlassButton() .accessibilityHint("Opens Settings / Gateway") - .accessibilityIdentifier("chat-gateway-status") } else { - self.connectionStatusIcon - .accessibilityLabel(self.gatewayAccessibilityLabel) + self.connectionPill } } - private var connectionStatusIcon: some View { - Image(systemName: self.gatewayStatusSymbol) - .font(.subheadline.weight(.semibold)) - .foregroundStyle(self.gatewayPillColor) - .frame(width: 44, height: 44) - } - - private var gatewayStatusSymbol: String { - switch self.gatewayDisplayState { - case .connected: - self.gatewayConnected ? "checkmark.circle.fill" : "exclamationmark.circle" - case .connecting: - "arrow.trianglehead.2.clockwise.rotate.90" - case .error: - "exclamationmark.triangle.fill" - case .disconnected: - "wifi.slash" + private var connectionPill: some View { + HStack(spacing: 6) { + ProStatusDot(color: self.gatewayPillColor) + Text(Self.gatewayPillTitle(state: self.gatewayDisplayState, isGatewayUsable: self.gatewayConnected)) + .font(OpenClawType.captionSemiBold) + .lineLimit(1) } - } - - private var gatewayAccessibilityLabel: String { - "Gateway: \(Self.gatewayPillTitle(state: self.gatewayDisplayState, isGatewayUsable: self.gatewayConnected))" + .foregroundStyle(self.gatewayPillColor) + .padding(.horizontal, 4) + .frame(height: 30) } private var gatewayConnected: Bool { diff --git a/apps/ios/Sources/Design/CommandCenterSupport.swift b/apps/ios/Sources/Design/CommandCenterSupport.swift index 9075fcd28b5c..ea7b041fd373 100644 --- a/apps/ios/Sources/Design/CommandCenterSupport.swift +++ b/apps/ios/Sources/Design/CommandCenterSupport.swift @@ -42,27 +42,27 @@ struct CommandSessionRow: View { var body: some View { HStack(alignment: .center, spacing: 12) { Image(systemName: self.item.icon) - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(self.item.color) .frame(width: 30, height: 30) .background { - RoundedRectangle(cornerRadius: 9, style: .continuous) + RoundedRectangle(cornerRadius: OpenClawRadius.sm, style: .continuous) .fill(self.item.color.opacity(0.12)) } VStack(alignment: .leading, spacing: 4) { HStack(alignment: .firstTextBaseline, spacing: 8) { Text(self.item.title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(1) .minimumScaleFactor(0.82) Spacer(minLength: 6) Text(self.item.trailing) - .font(.caption2.weight(.medium)) + .font(OpenClawType.caption2Medium) .foregroundStyle(.secondary) } HStack(spacing: 8) { Text(self.item.detail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(1) Spacer(minLength: 6) @@ -71,7 +71,7 @@ struct CommandSessionRow: View { .frame(width: 68) } Text(self.progressLabel) - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(self.item.color) .lineLimit(1) .frame(width: 48, alignment: .trailing) @@ -97,7 +97,7 @@ struct CommandSessionRow: View { struct CommandViewMoreRow: View { var body: some View { Label("View More", systemImage: "chevron.right") - .font(.subheadline.weight(.bold)) + .font(OpenClawType.subheadSemiBold) .foregroundStyle(OpenClawBrand.accent) .frame(maxWidth: .infinity) .padding(.vertical, 10) @@ -113,19 +113,19 @@ struct CommandEmptyStateRow: View { var body: some View { HStack(spacing: 10) { Image(systemName: self.icon) - .font(.caption.weight(.bold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(OpenClawBrand.ok) .frame(width: 30, height: 30) .background { - RoundedRectangle(cornerRadius: 8, style: .continuous) + RoundedRectangle(cornerRadius: OpenClawRadius.xs, style: .continuous) .fill(OpenClawBrand.ok.opacity(0.10)) } VStack(alignment: .leading, spacing: 2) { Text(self.title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(1) Text(self.detail) - .font(.caption2.weight(.medium)) + .font(OpenClawType.caption2Medium) .foregroundStyle(.secondary) .lineLimit(1) } diff --git a/apps/ios/Sources/Design/CommandCenterTab.swift b/apps/ios/Sources/Design/CommandCenterTab.swift index b45bad7085d3..7cd175d8475f 100644 --- a/apps/ios/Sources/Design/CommandCenterTab.swift +++ b/apps/ios/Sources/Design/CommandCenterTab.swift @@ -135,7 +135,7 @@ struct CommandCenterTab: View { } accessory: { Button(action: self.openSettings) { Image(systemName: "gearshape.fill") - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .frame(width: OpenClawProMetric.compactControlSize, height: OpenClawProMetric.compactControlSize) } .openClawGlassButton() @@ -195,15 +195,15 @@ struct CommandCenterTab: View { VStack(alignment: .leading, spacing: 6) { HStack(spacing: 5) { Image(systemName: icon) - .font(.caption2.weight(.bold)) + .font(OpenClawType.caption2Bold) .foregroundStyle(color) Text(title) - .font(.caption2.weight(.medium)) + .font(OpenClawType.caption2Medium) .foregroundStyle(.secondary) .lineLimit(1) } Text(value) - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(title == "Connection" ? color : .primary) .lineLimit(1) .minimumScaleFactor(0.72) @@ -275,7 +275,7 @@ struct CommandCenterTab: View { private func cardHeader(title: String) -> some View { HStack(spacing: 8) { Text(title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .foregroundStyle(.secondary) Spacer(minLength: 8) } @@ -659,9 +659,9 @@ struct CommandSessionsScreen: View { VStack(alignment: .leading, spacing: 4) { Text("Sessions") - .font(.system(size: 27, weight: .bold, design: .rounded)) + .font(OpenClawType.title2) Text(self.headerDetail) - .font(.caption.weight(.medium)) + .font(OpenClawType.captionMedium) .foregroundStyle(.secondary) } } @@ -673,7 +673,7 @@ struct CommandSessionsScreen: View { VStack(spacing: 0) { HStack(spacing: 8) { Text("Recent sessions") - .font(.subheadline.weight(.bold)) + .font(OpenClawType.subheadSemiBold) Spacer(minLength: 8) if self.isLoading { ProgressView() diff --git a/apps/ios/Sources/Design/IPadSkillWorkshopScreen.swift b/apps/ios/Sources/Design/IPadSkillWorkshopScreen.swift index 8e6d4d9e3ff8..e93abff60c7c 100644 --- a/apps/ios/Sources/Design/IPadSkillWorkshopScreen.swift +++ b/apps/ios/Sources/Design/IPadSkillWorkshopScreen.swift @@ -64,7 +64,7 @@ struct IPadSkillWorkshopScreen: View { ScrollView { self.presentedProposalDetail(proposalID: route.proposalID) .padding(.horizontal, OpenClawProMetric.pagePadding) - .padding(.vertical, 16) + .padding(.vertical, OpenClawSpacing.space4) } .background(OpenClawProBackground()) .navigationTitle("Proposal") @@ -130,12 +130,12 @@ struct IPadSkillWorkshopScreen: View { } if let noticeText { Text(noticeText) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(OpenClawBrand.accent) } if let errorText { Text(errorText) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(OpenClawBrand.warn) } } @@ -149,9 +149,9 @@ struct IPadSkillWorkshopScreen: View { HStack(alignment: .firstTextBaseline, spacing: 10) { VStack(alignment: .leading, spacing: 3) { Text("\(self.filteredProposals.count) proposals") - .font(.headline) + .font(OpenClawType.headline) Text(self.statusFilterLabel) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) } Spacer(minLength: 8) @@ -186,12 +186,12 @@ struct IPadSkillWorkshopScreen: View { } if let noticeText { Text(noticeText) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(OpenClawBrand.accent) } if let errorText { Text(errorText) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(OpenClawBrand.warn) } } @@ -202,12 +202,12 @@ struct IPadSkillWorkshopScreen: View { private var proposalSearchField: some View { HStack(spacing: 8) { Image(systemName: "magnifyingglass") - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(.secondary) TextField("Search proposals", text: self.$query) .textInputAutocapitalization(.never) .autocorrectionDisabled() - .font(.subheadline) + .font(OpenClawType.subhead) if !self.query.isEmpty { Button { self.query = "" @@ -223,7 +223,7 @@ struct IPadSkillWorkshopScreen: View { private var agentScopeMenu: some View { HStack(spacing: 8) { Text("Agent") - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(.secondary) Menu { Button("Default agent") { @@ -237,10 +237,10 @@ struct IPadSkillWorkshopScreen: View { } label: { HStack(spacing: 6) { Text(self.agentScopeLabel) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(1) Image(systemName: "chevron.up.chevron.down") - .font(.caption2.weight(.bold)) + .font(OpenClawType.caption2Bold) } .frame(maxWidth: .infinity, alignment: .trailing) } @@ -416,9 +416,9 @@ struct IPadSkillWorkshopScreen: View { ProIconBadge(systemName: "hammer", color: proposal.statusColor) VStack(alignment: .leading, spacing: 4) { Text(proposal.title) - .font(.headline) + .font(OpenClawType.headline) Text(proposal.description) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } @@ -432,23 +432,23 @@ struct IPadSkillWorkshopScreen: View { if let content = proposal.content, !content.isEmpty { Text(content) - .font(.caption.monospaced()) + .font(OpenClawType.monoSmall) .foregroundStyle(.secondary) .lineLimit(16) .textSelection(.enabled) } else { Text("Select refresh to load the proposal body.") - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) } if !proposal.supportFiles.isEmpty { VStack(alignment: .leading, spacing: 6) { Text("Support files") - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) ForEach(proposal.supportFiles, id: \.path) { file in Text(file.path) - .font(.caption2.monospaced()) + .font(OpenClawType.monoSmall) .foregroundStyle(.secondary) .lineLimit(1) } @@ -579,7 +579,7 @@ struct IPadSkillWorkshopScreen: View { Image(systemName: "lock.shield") .foregroundStyle(OpenClawBrand.warn) Text("Admin scope required.") - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(.secondary) Spacer(minLength: 8) } @@ -951,11 +951,11 @@ private struct IPadSkillProposalKanbanCard: View { color: self.proposal.statusColor) VStack(alignment: .leading, spacing: 4) { Text(self.proposal.title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .foregroundStyle(self.isSelected ? OpenClawBrand.accent : .primary) .lineLimit(2) Text(self.proposal.description) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(3) } @@ -964,7 +964,7 @@ private struct IPadSkillProposalKanbanCard: View { ProValuePill(value: self.proposal.status, color: self.proposal.statusColor) Spacer(minLength: 4) Text(self.proposal.ageLabel) - .font(.caption2.weight(.semibold)) + .font(OpenClawType.caption2SemiBold) .foregroundStyle(.secondary) } } @@ -1000,10 +1000,10 @@ private struct IPadSkillProposalKanbanCard: View { .disabled(self.isInspecting) } } - .padding(12) + .padding(OpenClawSpacing.space3) .background( self.isSelected ? OpenClawBrand.accent.opacity(0.08) : Color.clear, - in: RoundedRectangle(cornerRadius: 8, style: .continuous)) + in: RoundedRectangle(cornerRadius: OpenClawRadius.xs, style: .continuous)) .contentShape(Rectangle()) .contextMenu { Button("Inspect", action: self.inspect) @@ -1027,24 +1027,24 @@ struct IPadSkillProposalRow: View { ProIconBadge(systemName: self.isBusy ? "hourglass" : "hammer", color: self.proposal.statusColor) VStack(alignment: .leading, spacing: 4) { Text(self.proposal.title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .foregroundStyle(self.isSelected ? OpenClawBrand.accent : .primary) .lineLimit(1) Text(self.proposal.description) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(2) } Spacer(minLength: 8) Text(self.proposal.ageLabel) - .font(.caption2.weight(.semibold)) + .font(OpenClawType.caption2SemiBold) .foregroundStyle(.secondary) } .padding(.horizontal, 14) .padding(.vertical, 10) .background( self.isSelected ? OpenClawBrand.danger.opacity(0.08) : Color.clear, - in: RoundedRectangle(cornerRadius: 8, style: .continuous)) + in: RoundedRectangle(cornerRadius: OpenClawRadius.xs, style: .continuous)) } } diff --git a/apps/ios/Sources/Design/IPadWorkboardScreen.swift b/apps/ios/Sources/Design/IPadWorkboardScreen.swift index 481789ff10dd..319db227fe3f 100644 --- a/apps/ios/Sources/Design/IPadWorkboardScreen.swift +++ b/apps/ios/Sources/Design/IPadWorkboardScreen.swift @@ -105,12 +105,12 @@ struct IPadWorkboardScreen: View { self.boardScopeMenu HStack(spacing: 8) { Image(systemName: "magnifyingglass") - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(.secondary) TextField("Search cards", text: self.$query) .textInputAutocapitalization(.never) .autocorrectionDisabled() - .font(.subheadline) + .font(OpenClawType.subhead) if !self.query.isEmpty { Button { self.query = "" @@ -164,12 +164,12 @@ struct IPadWorkboardScreen: View { if let dispatchSummaryText { Text(dispatchSummaryText) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(OpenClawBrand.accent) } if let errorText { Text(errorText) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(OpenClawBrand.warn) } } @@ -182,7 +182,7 @@ struct IPadWorkboardScreen: View { VStack(alignment: .leading, spacing: 9) { HStack(alignment: .firstTextBaseline, spacing: 10) { Text("\(self.filteredCards.count) cards") - .font(.headline) + .font(OpenClawType.headline) Spacer(minLength: 8) self.compactRefreshButton } @@ -206,19 +206,19 @@ struct IPadWorkboardScreen: View { } } else { Text(Self.compactWriteUnavailableMessage(canRead: self.canRead)) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(.secondary) .lineLimit(2) } if let dispatchSummaryText { Text(dispatchSummaryText) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(OpenClawBrand.accent) } if let errorText { Text(errorText) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(OpenClawBrand.warn) } } @@ -231,7 +231,7 @@ struct IPadWorkboardScreen: View { Task { await self.loadCards(force: true) } } label: { Image(systemName: "arrow.clockwise") - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .frame(width: 32, height: 32) } .buttonStyle(.plain) @@ -266,19 +266,21 @@ struct IPadWorkboardScreen: View { } label: { HStack(spacing: 8) { Image(systemName: "rectangle.stack") - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) Text(self.boardScopeLabel) - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .lineLimit(1) Spacer(minLength: 4) Image(systemName: "chevron.up.chevron.down") - .font(.caption2.weight(.bold)) + .font(OpenClawType.caption2Bold) } .padding(.horizontal, 10) .frame(height: 32) - .background(Color.primary.opacity(0.06), in: RoundedRectangle(cornerRadius: 8, style: .continuous)) + .background( + Color.primary.opacity(0.06), + in: RoundedRectangle(cornerRadius: OpenClawRadius.xs, style: .continuous)) .overlay { - RoundedRectangle(cornerRadius: 8, style: .continuous) + RoundedRectangle(cornerRadius: OpenClawRadius.xs, style: .continuous) .strokeBorder(Color.primary.opacity(0.08), lineWidth: 1) } } @@ -313,7 +315,7 @@ struct IPadWorkboardScreen: View { self.selectedStatus = status } label: { Text(IPadWorkboardDefaults.label(for: status)) - .font(.caption2.weight(.semibold)) + .font(OpenClawType.caption2SemiBold) .lineLimit(1) .padding(.horizontal, 10) .frame(height: 30) @@ -339,7 +341,7 @@ struct IPadWorkboardScreen: View { private var boardScopeMenu: some View { HStack(spacing: 8) { Text("Board") - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(.secondary) Menu { Button("All boards") { @@ -353,10 +355,10 @@ struct IPadWorkboardScreen: View { } label: { HStack(spacing: 6) { Text(self.boardScopeLabel) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(1) Image(systemName: "chevron.up.chevron.down") - .font(.caption2.weight(.bold)) + .font(OpenClawType.caption2Bold) } .frame(maxWidth: .infinity, alignment: .trailing) } @@ -370,7 +372,7 @@ struct IPadWorkboardScreen: View { private var statusMenu: some View { HStack(spacing: 8) { Text("Status") - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(.secondary) Menu { Button("Active") { @@ -384,9 +386,9 @@ struct IPadWorkboardScreen: View { } label: { HStack(spacing: 6) { Text(IPadWorkboardDefaults.label(for: self.selectedStatus)) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) Image(systemName: "chevron.up.chevron.down") - .font(.caption2.weight(.bold)) + .font(OpenClawType.caption2Bold) } .frame(maxWidth: .infinity, alignment: .trailing) } @@ -975,10 +977,10 @@ private struct IPadWorkboardKanbanCard: View { ProIconBadge(systemName: self.icon, color: self.color) VStack(alignment: .leading, spacing: 4) { Text(self.card.title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(2) Text(self.detail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(3) } @@ -986,7 +988,7 @@ private struct IPadWorkboardKanbanCard: View { if !self.card.labels.isEmpty { Text(self.card.labels.prefix(3).joined(separator: ", ")) - .font(.caption2.weight(.medium)) + .font(OpenClawType.caption2Medium) .foregroundStyle(.secondary) .lineLimit(1) } @@ -1025,7 +1027,7 @@ private struct IPadWorkboardKanbanCard: View { ProValuePill(value: IPadWorkboardDefaults.label(for: self.card.status), color: self.color) } } - .padding(12) + .padding(OpenClawSpacing.space3) .contentShape(Rectangle()) } @@ -1078,10 +1080,10 @@ struct IPadWorkboardQueueRow: View { ProIconBadge(systemName: self.icon, color: self.color) VStack(alignment: .leading, spacing: 5) { Text(self.card.title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(2) Text(self.detail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(2) } @@ -1097,7 +1099,7 @@ struct IPadWorkboardQueueRow: View { self.actionMenuItems } label: { Image(systemName: self.isBusy ? "hourglass" : "ellipsis.circle") - .font(.system(size: 19, weight: .semibold)) + .font(OpenClawType.headline) .frame(width: 36, height: 36) .contentShape(Rectangle()) } diff --git a/apps/ios/Sources/Design/OpenClawBrand.swift b/apps/ios/Sources/Design/OpenClawBrand.swift index f55ce0f72125..d4cc40149822 100644 --- a/apps/ios/Sources/Design/OpenClawBrand.swift +++ b/apps/ios/Sources/Design/OpenClawBrand.swift @@ -61,6 +61,13 @@ enum OpenClawBrand { static let uiAccentForeground = adaptiveUIColor(light: (183, 56, 51), dark: (255, 107, 102)) static let uiAccentHot = adaptiveUIColor(light: (204, 75, 69), dark: (232, 92, 86)) static let uiAccentHotForeground = adaptiveUIColor(light: (166, 55, 50), dark: (255, 123, 115)) + static let uiAccentPressed = adaptiveUIColor(light: (148, 40, 36), dark: (148, 40, 36)) + static let uiTeal = adaptiveUIColor(light: (0, 196, 176), dark: (0, 196, 176)) + static let uiVoid = adaptiveUIColor(light: (246, 247, 249), dark: (11, 12, 17)) + static let uiObsidian = adaptiveUIColor(light: (255, 255, 255), dark: (19, 21, 28)) + static let uiSlate = adaptiveUIColor(light: (242, 243, 247), dark: (28, 31, 43)) + static let uiTextPrimary = adaptiveUIColor(light: (11, 12, 17), dark: (242, 239, 232)) + static let uiTextSecondary = adaptiveUIColor(light: (90, 94, 110), dark: (168, 170, 191)) static let uiOK = adaptiveUIColor(light: (19, 122, 62), dark: (48, 209, 88)) static let uiWarn = adaptiveUIColor(light: (154, 87, 0), dark: (255, 214, 10)) static let uiDanger = adaptiveUIColor(light: (185, 28, 28), dark: (252, 165, 165)) @@ -70,12 +77,19 @@ enum OpenClawBrand { static let accentForeground = Color(uiColor: Self.uiAccentForeground) static let accentHot = Color(uiColor: Self.uiAccentHot) static let accentHotForeground = Color(uiColor: Self.uiAccentHotForeground) + static let accentPressed = Color(uiColor: Self.uiAccentPressed) + static let teal = Color(uiColor: Self.uiTeal) + static let void = Color(uiColor: Self.uiVoid) + static let obsidian = Color(uiColor: Self.uiObsidian) + static let slate = Color(uiColor: Self.uiSlate) + static let textPrimary = Color(uiColor: Self.uiTextPrimary) + static let textSecondary = Color(uiColor: Self.uiTextSecondary) static let danger = Color(uiColor: Self.uiDanger) static let ok = Color(uiColor: Self.uiOK) static let warn = Color(uiColor: Self.uiWarn) static let info = Color(uiColor: Self.uiInfo) - static let graphite = Color(uiColor: adaptiveUIColor(light: (246, 247, 249), dark: (11, 12, 17))) - static let graphiteElevated = Color(uiColor: adaptiveUIColor(light: (255, 255, 255), dark: (19, 21, 28))) + static let graphite = void + static let graphiteElevated = obsidian static var sheetBackground: LinearGradient { LinearGradient( diff --git a/apps/ios/Sources/Design/OpenClawDocsScreen.swift b/apps/ios/Sources/Design/OpenClawDocsScreen.swift index 9b3c0d3a2124..a2808f5b94b2 100644 --- a/apps/ios/Sources/Design/OpenClawDocsScreen.swift +++ b/apps/ios/Sources/Design/OpenClawDocsScreen.swift @@ -112,15 +112,15 @@ struct OpenClawDocsScreen: View { ProIconBadge(systemName: icon, color: OpenClawBrand.accent) VStack(alignment: .leading, spacing: 3) { Text(title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) Text(detail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(1) } Spacer(minLength: 8) Image(systemName: "arrow.up.right") - .font(.caption.weight(.bold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(.secondary) } .padding(.horizontal, 14) diff --git a/apps/ios/Sources/Design/OpenClawProComponents.swift b/apps/ios/Sources/Design/OpenClawProComponents.swift index 903670403219..e3298b75a3c7 100644 --- a/apps/ios/Sources/Design/OpenClawProComponents.swift +++ b/apps/ios/Sources/Design/OpenClawProComponents.swift @@ -8,6 +8,23 @@ enum OpenClawProMetric { static let bottomScrollInset: CGFloat = 96 } +enum OpenClawSpacing { + static let space1: CGFloat = 4 + static let space2: CGFloat = 8 + static let space3: CGFloat = 12 + static let space4: CGFloat = 16 + static let space6: CGFloat = 24 + static let space8: CGFloat = 32 +} + +enum OpenClawRadius { + static let xs: CGFloat = 8 + static let sm: CGFloat = 10 + static let md: CGFloat = 12 + static let lg: CGFloat = 16 + static let xl: CGFloat = 20 +} + struct OpenClawProBackground: View { var body: some View { Color(uiColor: .systemGroupedBackground) @@ -24,18 +41,18 @@ struct ProSectionHeader: View { var body: some View { HStack { Text(self.title) - .font(.footnote.weight(.medium)) + .font(OpenClawType.footnoteMedium) .foregroundStyle(.secondary) .textCase(self.uppercase ? .uppercase : nil) Spacer() if let actionTitle { if let action { Button(actionTitle, action: action) - .font(.footnote.weight(.medium)) - .foregroundStyle(OpenClawBrand.accentForeground) + .font(OpenClawType.footnoteMedium) + .foregroundStyle(OpenClawBrand.accent) } else { Text(actionTitle) - .font(.footnote.weight(.medium)) + .font(OpenClawType.footnoteMedium) .foregroundStyle(.secondary) } } @@ -69,7 +86,7 @@ private struct ProPanelBackground: View { let isProminent: Bool var body: some View { - let shape = RoundedRectangle(cornerRadius: self.radius, style: .continuous) + let shape = RoundedRectangle(cornerRadius: radius, style: .continuous) shape .fill(self.fill) .overlay { @@ -96,7 +113,7 @@ private struct ProInsetSurfaceModifier: ViewModifier { let radius: CGFloat func body(content: Content) -> some View { - let shape = RoundedRectangle(cornerRadius: self.radius, style: .continuous) + let shape = RoundedRectangle(cornerRadius: radius, style: .continuous) content.background { shape .fill(Color(uiColor: .tertiarySystemGroupedBackground)) @@ -166,26 +183,26 @@ extension View { radius: CGFloat = OpenClawProMetric.cardRadius, isProminent: Bool = false) -> some View { - self.modifier(ProPanelSurfaceModifier( + modifier(ProPanelSurfaceModifier( tint: tint, radius: radius, isProminent: isProminent)) } func proInsetSurface(tint: Color, radius: CGFloat) -> some View { - self.modifier(ProInsetSurfaceModifier(tint: tint, radius: radius)) + modifier(ProInsetSurfaceModifier(tint: tint, radius: radius)) } func openClawGlassButton(prominent: Bool = false, tint: Color? = nil) -> some View { - self.modifier(OpenClawGlassButtonModifier(prominent: prominent, tint: tint)) + modifier(OpenClawGlassButtonModifier(prominent: prominent, tint: tint)) } func openClawTabBarBehavior() -> some View { - self.modifier(OpenClawTabBarBehaviorModifier()) + modifier(OpenClawTabBarBehaviorModifier()) } func openClawGlassSurface(radius: CGFloat = OpenClawProMetric.controlRadius) -> some View { - self.modifier(OpenClawGlassSurfaceModifier(radius: radius)) + modifier(OpenClawGlassSurfaceModifier(radius: radius)) } } @@ -218,11 +235,11 @@ struct ProIconBadge: View { var body: some View { Image(systemName: self.systemName) - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(self.color) .frame(width: 30, height: 30) .background { - RoundedRectangle(cornerRadius: 8, style: .continuous) + RoundedRectangle(cornerRadius: OpenClawRadius.xs, style: .continuous) .fill(self.color.opacity(0.12)) } } @@ -255,9 +272,9 @@ struct OpenClawSidebarRevealButton: View { } var body: some View { - let button = Button(action: self.headerAction.action) { + let button = Button(action: headerAction.action) { Image(systemName: self.headerAction.systemName) - .font(.system(size: 16, weight: .semibold)) + .font(OpenClawType.subhead) .frame( width: OpenClawProMetric.compactControlSize, height: OpenClawProMetric.compactControlSize) @@ -267,7 +284,7 @@ struct OpenClawSidebarRevealButton: View { .openClawGlassButton(tint: OpenClawBrand.accent) .accessibilityLabel(self.headerAction.accessibilityLabel) - if let accessibilityIdentifier = self.headerAction.accessibilityIdentifier { + if let accessibilityIdentifier = headerAction.accessibilityIdentifier { button.accessibilityIdentifier(accessibilityIdentifier) } else { button @@ -324,16 +341,16 @@ struct OpenClawNoticeBanner: View { VStack(alignment: .leading, spacing: 6) { HStack(alignment: .firstTextBaseline, spacing: 8) { Text(self.title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .multilineTextAlignment(.leading) Spacer(minLength: 0) Text(self.ownerLabel) - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(.secondary) } Text(self.message) - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) @@ -367,12 +384,12 @@ struct OpenClawNoticeBanner: View { switch detail { case let .accent(value): Text(value) - .font(.caption.weight(.medium)) + .font(OpenClawType.captionMedium) .foregroundStyle(self.tint) .fixedSize(horizontal: false, vertical: true) case let .requestID(value): Text("Request ID: \(value)") - .font(.system(.caption, design: .monospaced).weight(.medium)) + .font(OpenClawType.monoSmall) .foregroundStyle(.secondary) .textSelection(.enabled) } @@ -383,8 +400,8 @@ struct OpenClawNoticeBanner: View { struct OpenClawAdaptiveHeaderRow: View { let title: String let subtitle: String? - var titleFont: Font = .title3.weight(.semibold) - var subtitleFont: Font = .subheadline + var titleFont: Font = OpenClawType.title3 + var subtitleFont: Font = OpenClawType.subhead var subtitleLineLimit: Int? = 2 @ViewBuilder let leading: Leading @ViewBuilder let accessory: Accessory @@ -392,8 +409,8 @@ struct OpenClawAdaptiveHeaderRow: View { init( title: String, subtitle: String? = nil, - titleFont: Font = .title3.weight(.semibold), - subtitleFont: Font = .subheadline, + titleFont: Font = OpenClawType.title3, + subtitleFont: Font = OpenClawType.subhead, subtitleLineLimit: Int? = 2, @ViewBuilder leading: () -> Leading, @ViewBuilder accessory: () -> Accessory) @@ -465,6 +482,117 @@ struct OpenClawAdaptiveHeaderRow: View { } } +/// Shared switch indicator replacing the 3 duplicated capsule toggles. +/// Native Toggle only hits the switch edge on iOS 26; this full-width button approach +/// gives the whole row a tap target. +struct OpenClawToggleIndicator: View { + let isOn: Bool + + var body: some View { + Capsule() + .fill(self.isOn ? OpenClawBrand.accent : Color.secondary.opacity(0.35)) + .frame(width: 52, height: 32) + .overlay(alignment: self.isOn ? .trailing : .leading) { + Circle() + .fill(Color.white) + .frame(width: 28, height: 28) + .padding(2) + .shadow(color: Color.black.opacity(0.14), radius: 1, x: 0, y: 1) + } + } +} + +enum OpenClawStatusTone { + case ok + case warn + case danger + case info + case accent + case teal + case muted + + var color: Color { + switch self { + case .ok: OpenClawBrand.ok + case .warn: OpenClawBrand.warn + case .danger: OpenClawBrand.danger + case .info: OpenClawBrand.info + case .accent: OpenClawBrand.accent + case .teal: OpenClawBrand.teal + case .muted: OpenClawBrand.textSecondary + } + } +} + +struct OpenClawStatusBadge: View { + @Environment(\.colorScheme) private var colorScheme + let label: String + let tone: OpenClawStatusTone + + var body: some View { + HStack(spacing: OpenClawSpacing.space1 + 2) { + Circle() + .fill(self.tone.color) + .frame(width: 7, height: 7) + .shadow(color: self.tone.color.opacity(0.55), radius: 3) + Text(self.label) + .font(OpenClawType.caption2SemiBold) + .foregroundStyle(self.tone.color) + } + .padding(.horizontal, OpenClawSpacing.space2) + .padding(.vertical, 5) + .background { + Capsule() + .fill(self.tone.color.opacity(self.colorScheme == .dark ? 0.14 : 0.10)) + } + } +} + +struct OpenClawPrimaryButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .font(OpenClawType.headline) + .foregroundStyle(.white) + .frame(maxWidth: .infinity, minHeight: 48) + .background { + RoundedRectangle(cornerRadius: OpenClawRadius.sm, style: .continuous) + .fill( + configuration.isPressed + ? OpenClawBrand.accentPressed + : OpenClawBrand.accent) + } + .animation(.easeOut(duration: 0.15), value: configuration.isPressed) + } +} + +struct OpenClawSecondaryButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .font(OpenClawType.headline) + .foregroundStyle(OpenClawBrand.textPrimary) + .frame(maxWidth: .infinity, minHeight: 48) + .background { + RoundedRectangle(cornerRadius: OpenClawRadius.sm, style: .continuous) + .fill(Color(uiColor: .secondarySystemBackground)) + .overlay { + RoundedRectangle(cornerRadius: OpenClawRadius.sm, style: .continuous) + .strokeBorder(Color(uiColor: .separator).opacity(0.35), lineWidth: 1) + } + } + .opacity(configuration.isPressed ? 0.82 : 1) + } +} + +extension View { + func openClawPrimaryButton() -> some View { + self.buttonStyle(OpenClawPrimaryButtonStyle()) + } + + func openClawSecondaryButton() -> some View { + self.buttonStyle(OpenClawSecondaryButtonStyle()) + } +} + struct ProStatusDot: View { var color: Color @@ -482,7 +610,7 @@ struct ProValuePill: View { var body: some View { Text(self.value) - .font(.footnote.weight(.semibold)) + .font(OpenClawType.footnoteSemiBold) .foregroundStyle(self.color) .lineLimit(1) .padding(.horizontal, 8) @@ -531,18 +659,8 @@ struct OpenClawGatewayCompactPill: View { @Environment(NodeAppModel.self) private var appModel var body: some View { - HStack(spacing: 6) { - Image(systemName: self.icon) - .font(.caption.weight(.semibold)) - Text(self.title) - .font(.caption.weight(.semibold)) - .lineLimit(1) - } - .foregroundStyle(self.color) - .padding(.horizontal, 4) - .frame(minHeight: 30) - .fixedSize(horizontal: true, vertical: false) - .accessibilityLabel("Gateway \(self.title)") + OpenClawStatusBadge(label: self.title, tone: self.tone) + .accessibilityLabel("Gateway \(self.title)") } private var title: String { @@ -558,29 +676,16 @@ struct OpenClawGatewayCompactPill: View { } } - private var color: Color { + private var tone: OpenClawStatusTone { switch GatewayStatusBuilder.build(appModel: self.appModel) { case .connected: - OpenClawBrand.ok + .ok case .connecting: - OpenClawBrand.accent + .accent case .error: - OpenClawBrand.warn + .warn case .disconnected: - .secondary - } - } - - private var icon: String { - switch GatewayStatusBuilder.build(appModel: self.appModel) { - case .connected: - "checkmark.circle.fill" - case .connecting: - "arrow.triangle.2.circlepath" - case .error: - "exclamationmark.triangle.fill" - case .disconnected: - "wifi.slash" + .muted } } } @@ -596,7 +701,7 @@ struct ProMetricTile: View { VStack(alignment: .leading, spacing: 10) { HStack { Image(systemName: self.icon) - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(self.color) .frame(width: 24, height: 24) .background(self.color.opacity(self.colorScheme == .dark ? 0.18 : 0.10), in: Circle()) @@ -605,11 +710,11 @@ struct ProMetricTile: View { VStack(alignment: .leading, spacing: 2) { Text(self.value) - .font(.headline.weight(.bold)) + .font(OpenClawType.headline) .lineLimit(1) .minimumScaleFactor(0.72) Text(self.title) - .font(.caption2.weight(.medium)) + .font(OpenClawType.caption2Medium) .foregroundStyle(.secondary) .lineLimit(1) } @@ -666,10 +771,10 @@ struct ProPanelHeader: View { var body: some View { HStack(spacing: 8) { Text(self.title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) if let value { Text(value) - .font(.caption2.weight(.bold)) + .font(OpenClawType.caption2Bold) .foregroundStyle(.secondary) } Spacer(minLength: 8) @@ -687,11 +792,11 @@ struct ProPanelHeader: View { Button(action: action) { Image(systemName: actionIcon) } - .accessibilityLabel(self.actionAccessibilityLabel ?? self.actionTitle ?? self.title) + .accessibilityLabel(self.actionAccessibilityLabel ?? actionTitle ?? self.title) .disabled(self.isActionDisabled) } else if let actionTitle { Button(actionTitle, action: action) - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .disabled(self.isActionDisabled) } } @@ -712,10 +817,10 @@ struct ProStatusRow: View { ProIconBadge(systemName: self.icon, color: self.color) VStack(alignment: .leading, spacing: 4) { Text(self.title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(1) Text(self.detail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(2) } @@ -726,7 +831,7 @@ struct ProStatusRow: View { } if let actionTitle, let action { Button(actionTitle, action: action) - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .buttonStyle(.bordered) .controlSize(.mini) } diff --git a/apps/ios/Sources/Design/OpenClawTypography.swift b/apps/ios/Sources/Design/OpenClawTypography.swift new file mode 100644 index 000000000000..95b8238ff509 --- /dev/null +++ b/apps/ios/Sources/Design/OpenClawTypography.swift @@ -0,0 +1,107 @@ +import SwiftUI +import UIKit + +enum OpenClawType { + // Display — Plus Jakarta Sans + static let title1 = scaledDisplay(name: Display.extraBold, size: 34, relativeTo: .largeTitle) + static let title2 = scaledDisplay(name: Display.bold, size: 28, relativeTo: .title1) + static let title3 = scaledDisplay(name: Display.bold, size: 22, relativeTo: .title2) + static let headline = scaledDisplay(name: Display.semiBold, size: 17, relativeTo: .headline) + // Body — DM Sans + static let body = scaledBody(name: Body.regular, size: 17, relativeTo: .body) + static let callout = scaledBody(name: Body.regular, size: 16, relativeTo: .callout) + static let subhead = scaledBody(name: Body.medium, size: 15, relativeTo: .subheadline) + static let subheadSemiBold = scaledDisplay(name: Display.semiBold, size: 15, relativeTo: .subheadline) + static let footnote = scaledBody(name: Body.regular, size: 13, relativeTo: .footnote) + static let footnoteMedium = scaledBody(name: Body.medium, size: 13, relativeTo: .footnote) + static let footnoteSemiBold = scaledBody(name: Body.semiBold, size: 13, relativeTo: .footnote) + static let caption = scaledBody(name: Body.regular, size: 12, relativeTo: .caption1) + static let captionMedium = scaledBody(name: Body.medium, size: 12, relativeTo: .caption1) + static let captionSemiBold = scaledBody(name: Body.semiBold, size: 12, relativeTo: .caption1) + static let caption2 = scaledBody(name: Body.regular, size: 11, relativeTo: .caption2) + static let caption2Medium = scaledBody(name: Body.medium, size: 11, relativeTo: .caption2) + static let caption2SemiBold = scaledBody(name: Body.semiBold, size: 11, relativeTo: .caption2) + static let caption2Bold = scaledDisplay(name: Display.bold, size: 11, relativeTo: .caption2) + static let title2SemiBold = scaledDisplay(name: Display.semiBold, size: 28, relativeTo: .title1) + + // Mono — JetBrains Mono + static let mono = scaledMono(name: Mono.regular, size: 14, relativeTo: .body) + static let monoSmall = scaledMono(name: Mono.regular, size: 12, relativeTo: .caption1) + static let monoFootnote = scaledMono(name: Mono.regular, size: 13, relativeTo: .footnote) + static let monoHeadline = scaledMono(name: Mono.medium, size: 17, relativeTo: .headline) + + /// PostScript names for bundled fonts. Keep aligned with `UIAppFonts` in `project.yml`. + static let registeredPostScriptNames: [String] = [ + Display.light, + Display.regular, + Display.medium, + Display.semiBold, + Display.bold, + Display.extraBold, + Body.light, + Body.regular, + Body.italic, + Body.medium, + Body.semiBold, + Mono.regular, + Mono.medium, + Mono.semiBold, + ] + + private enum Display { + static let light = "PlusJakartaSans-Light" + static let regular = "PlusJakartaSans-Regular" + static let medium = "PlusJakartaSans-Medium" + static let semiBold = "PlusJakartaSans-SemiBold" + static let bold = "PlusJakartaSans-Bold" + static let extraBold = "PlusJakartaSans-ExtraBold" + } + + private enum Body { + static let light = "DMSans-Light" + static let regular = "DMSans-Regular" + static let italic = "DMSans-Italic" + static let medium = "DMSans-Medium" + static let semiBold = "DMSans-SemiBold" + } + + private enum Mono { + static let regular = "JetBrainsMono-Regular" + static let medium = "JetBrainsMono-Medium" + static let semiBold = "JetBrainsMono-SemiBold" + } + + private static func scaledDisplay( + name: String, + size: CGFloat, + relativeTo textStyle: UIFont.TextStyle) -> Font + { + self.scaledFont(name: name, size: size, relativeTo: textStyle) + } + + private static func scaledBody( + name: String, + size: CGFloat, + relativeTo textStyle: UIFont.TextStyle) -> Font + { + self.scaledFont(name: name, size: size, relativeTo: textStyle) + } + + private static func scaledMono( + name: String, + size: CGFloat, + relativeTo textStyle: UIFont.TextStyle) -> Font + { + self.scaledFont(name: name, size: size, relativeTo: textStyle) + } + + private static func scaledFont( + name: String, + size: CGFloat, + relativeTo textStyle: UIFont.TextStyle) -> Font + { + let base = UIFont(name: name, size: size) ?? UIFont.systemFont(ofSize: size) + let scaled = UIFontMetrics(forTextStyle: textStyle).scaledFont(for: base) + return Font(scaled) + } +} diff --git a/apps/ios/Sources/Design/SettingsChannelsDestination.swift b/apps/ios/Sources/Design/SettingsChannelsDestination.swift index 5d64e38260b0..8c1a0b813edd 100644 --- a/apps/ios/Sources/Design/SettingsChannelsDestination.swift +++ b/apps/ios/Sources/Design/SettingsChannelsDestination.swift @@ -36,9 +36,9 @@ struct SettingsChannelsDestination: View { ProIconBadge(systemName: "point.3.connected.trianglepath.dotted", color: self.summaryColor) VStack(alignment: .leading, spacing: 3) { Text("Channels / Integrations") - .font(.headline) + .font(OpenClawType.headline) Text(self.summaryDetail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } @@ -346,14 +346,14 @@ private struct SettingsChannelRow: View { ProIconBadge(systemName: self.entry.systemImage, color: self.entry.color) VStack(alignment: .leading, spacing: 4) { Text(self.entry.label) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) Text(self.entry.detailText) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) if let lastError = self.entry.lastError { Text(lastError) - .font(.caption2.weight(.medium)) + .font(OpenClawType.caption2Medium) .foregroundStyle(OpenClawBrand.warn) .lineLimit(2) } @@ -384,9 +384,9 @@ private struct SettingsChannelRow: View { .frame(width: 28, height: 28) VStack(alignment: .leading, spacing: 2) { Text(account.displayName) - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) Text(account.detailText) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(.secondary) .lineLimit(1) } @@ -628,7 +628,7 @@ private struct SettingsChannelsStatesPreview: View { { VStack(alignment: .leading, spacing: 8) { Text(title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .foregroundStyle(.secondary) ProCard(padding: 0, radius: SettingsLayout.cardRadius) { VStack(spacing: 0) { diff --git a/apps/ios/Sources/Design/SettingsProTab.swift b/apps/ios/Sources/Design/SettingsProTab.swift index 714d55f5a067..db2845b9add1 100644 --- a/apps/ios/Sources/Design/SettingsProTab.swift +++ b/apps/ios/Sources/Design/SettingsProTab.swift @@ -285,12 +285,12 @@ struct HostedPushRelayDisclosureSheet: View { ScrollView { VStack(alignment: .leading, spacing: 18) { Image(systemName: "network") - .font(.title2.weight(.semibold)) + .font(OpenClawType.title2SemiBold) .foregroundStyle(OpenClawBrand.accentForeground) Text("Enable OpenClaw Hosted Push Relay?") - .font(.title3.weight(.semibold)) + .font(OpenClawType.title3) Text(self.message) - .font(.body) + .font(OpenClawType.body) .foregroundStyle(.secondary) } .frame(maxWidth: .infinity, alignment: .leading) diff --git a/apps/ios/Sources/Design/SettingsProTabActions.swift b/apps/ios/Sources/Design/SettingsProTabActions.swift index 9d43af7e2e57..5d9f1b4b973e 100644 --- a/apps/ios/Sources/Design/SettingsProTabActions.swift +++ b/apps/ios/Sources/Design/SettingsProTabActions.swift @@ -17,9 +17,9 @@ extension SettingsProTab { ProIconBadge(systemName: icon, color: color) VStack(alignment: .leading, spacing: 3) { Text(title) - .font(.headline) + .font(OpenClawType.headline) Text(detail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(2) } @@ -100,9 +100,9 @@ extension SettingsProTab { ProIconBadge(systemName: icon, color: color) VStack(alignment: .leading, spacing: 3) { Text(title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) Text(detail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(1) } @@ -123,11 +123,11 @@ extension SettingsProTab { func detailRow(_ label: String, value: String) -> some View { HStack { Text(label) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) Spacer(minLength: 8) Text(value) - .font(.caption) + .font(OpenClawType.caption) .lineLimit(1) .truncationMode(.middle) } @@ -427,7 +427,11 @@ extension SettingsProTab { } else { self.locationModeRaw = previous self.previousLocationModeRaw = previous - self.locationStatusText = "Location permission was not granted." + if mode == .always { + self.locationStatusText = "Always Allow was not granted. Open iOS Settings → OpenClaw → Location to enable it." + } else { + self.locationStatusText = "Location permission was not granted. Check iOS Settings → OpenClaw → Location." + } self.refreshLocationPermissionSummary( desiredMode: OpenClawLocationMode(rawValue: previous) ?? .off) } diff --git a/apps/ios/Sources/Design/SettingsProTabSections.swift b/apps/ios/Sources/Design/SettingsProTabSections.swift index 57886a87c056..c1ba5b604527 100644 --- a/apps/ios/Sources/Design/SettingsProTabSections.swift +++ b/apps/ios/Sources/Design/SettingsProTabSections.swift @@ -1,3 +1,4 @@ +import CoreLocation import OpenClawKit import SwiftUI @@ -76,10 +77,10 @@ extension SettingsProTab { VStack(alignment: .leading, spacing: 3) { Text("Gateway") - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(1) Text(self.gatewaySummaryDetail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(1) .truncationMode(.middle) @@ -190,10 +191,10 @@ extension SettingsProTab { ProIconBadge(systemName: icon, color: color) VStack(alignment: .leading, spacing: 2) { Text(title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) if let detail, !detail.isEmpty { Text(detail) - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(.secondary) .lineLimit(1) } @@ -315,12 +316,12 @@ extension SettingsProTab { ProIconBadge(systemName: "bell.slash.fill", color: OpenClawBrand.warn) VStack(alignment: .leading, spacing: 4) { Text("Notifications are off") - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) Text( """ Enable Notifications to receive approval notifications while OpenClaw is not open. """) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } @@ -356,7 +357,7 @@ extension SettingsProTab { if let errorText = self.appModel.pendingExecApprovalPromptErrorText { Text(errorText) - .font(.caption2.weight(.medium)) + .font(OpenClawType.caption2Medium) .foregroundStyle(OpenClawBrand.danger) } @@ -397,9 +398,9 @@ extension SettingsProTab { ProIconBadge(systemName: "checkmark.shield.fill", color: OpenClawBrand.ok) VStack(alignment: .leading, spacing: 3) { Text("No approvals waiting") - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) Text(self.approvalEmptyDetail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(2) } @@ -533,7 +534,7 @@ extension SettingsProTab { .disabled(self.notificationStatus == .checking || self.isRequestingNotificationAuthorization) Text(self.notificationStatusDetail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) @@ -541,11 +542,11 @@ extension SettingsProTab { HStack(alignment: .top, spacing: 10) { Image(systemName: "network") - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(OpenClawBrand.accent) .frame(width: 22, height: 22) Text(self.notificationRelayDetail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } @@ -641,9 +642,9 @@ extension SettingsProTab { Button(action: action) { HStack(spacing: 7) { Image(systemName: isBusy ? "hourglass" : icon) - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) Text(title) - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .lineLimit(1) .minimumScaleFactor(0.76) } @@ -669,9 +670,9 @@ extension SettingsProTab { ProIconBadge(systemName: icon, color: isOn.wrappedValue ? OpenClawBrand.accent : .secondary) VStack(alignment: .leading, spacing: 3) { Text(title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) Text(detail) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(2) } @@ -692,9 +693,9 @@ extension SettingsProTab { .accent) VStack(alignment: .leading, spacing: 3) { Text("Location") - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) Text("Controls whether location can be shared with gateway tools.") - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(2) } @@ -714,13 +715,17 @@ extension SettingsProTab { .disabled(self.isChangingLocationMode) Text(self.locationPermissionDetailText) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle( self.locationPermissionSummary.needsAttention ? OpenClawBrand.warn : .secondary) + if self.locationModeRaw == OpenClawLocationMode.always.rawValue { + self.locationAlwaysGuidance + } + if let locationPermissionWarningText { Text(locationPermissionWarningText) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(OpenClawBrand.warn) } } @@ -728,11 +733,62 @@ extension SettingsProTab { .padding(.horizontal, OpenClawProMetric.pagePadding) } + @ViewBuilder + private var locationAlwaysGuidance: some View { + let osStatus = CLLocationManager().authorizationStatus + switch osStatus { + case .authorizedAlways: + Label { + Text("Always Allow is active.") + } icon: { + Image(systemName: "checkmark.circle") + } + .font(OpenClawType.caption) + .foregroundStyle(OpenClawBrand.ok) + case .authorizedWhenInUse: + VStack(alignment: .leading, spacing: 6) { + Label { + Text("iOS is set to While Using.") + + Text(" Change to Always in Settings for reliable background automations.") + } icon: { + Image(systemName: "exclamationmark.triangle") + } + .font(OpenClawType.caption) + .foregroundStyle(OpenClawBrand.warn) + self.openSettingsButton + } + case .denied, .restricted: + VStack(alignment: .leading, spacing: 6) { + Label { + Text("Location access is denied.") + + Text(" Enable it in Settings.") + } icon: { + Image(systemName: "xmark.circle") + } + .font(OpenClawType.caption) + .foregroundStyle(OpenClawBrand.danger) + self.openSettingsButton + } + default: + EmptyView() + } + } + + private var openSettingsButton: some View { + Button("Open Settings") { + guard let url = URL( + string: UIApplication.openSettingsURLString) else { return } + UIApplication.shared.open(url) + } + .font(OpenClawType.caption) + .buttonStyle(.bordered) + } + var agentSelectionCard: some View { ProCard(radius: SettingsLayout.cardRadius) { VStack(alignment: .leading, spacing: 10) { Text("Default Agent") - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) Picker("Agent", selection: self.$selectedAgentPickerId) { Text("Default").tag("") let defaultId = (self.appModel.gatewayDefaultAgentId ?? "") @@ -743,7 +799,7 @@ extension SettingsProTab { } } Text("Used for new Chat and Talk sessions.") - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) } } @@ -754,7 +810,7 @@ extension SettingsProTab { ProCard(radius: SettingsLayout.cardRadius) { VStack(alignment: .leading, spacing: 12) { Text("Setup Code") - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) TextField("Paste setup code", text: self.$setupCode) .textInputAutocapitalization(.never) .autocorrectionDisabled() @@ -780,13 +836,13 @@ extension SettingsProTab { } if let status = self.setupStatusLine { Text(status) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .textSelection(.enabled) } if let warning = self.tailnetWarningText { Text(warning) - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(OpenClawBrand.warn) } } @@ -798,10 +854,10 @@ extension SettingsProTab { ProCard(radius: SettingsLayout.cardRadius) { VStack(alignment: .leading, spacing: 12) { Text("Discovered Gateways") - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) if self.gatewayController.gateways.isEmpty { Text("No gateways found yet. Use manual setup if Bonjour is blocked.") - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) } else { ForEach(self.gatewayController.gateways) { gateway in @@ -820,9 +876,9 @@ extension SettingsProTab { HStack(spacing: 12) { VStack(alignment: .leading, spacing: 3) { Text(verbatim: gateway.name) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) Text(verbatim: self.gatewayDetailLines(gateway).joined(separator: " • ")) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) .lineLimit(2) } @@ -983,7 +1039,7 @@ extension SettingsProTab { } .buttonStyle(.bordered) Text(self.appModel.lastShareEventText) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) } } @@ -1050,7 +1106,7 @@ extension SettingsProTab { Spacer(minLength: 8) self.settingsSwitchIndicator(isOn: isOn.wrappedValue) } - .font(.subheadline) + .font(OpenClawType.subhead) .contentShape(Rectangle()) } .buttonStyle(.plain) @@ -1083,9 +1139,9 @@ extension SettingsProTab { .lineLimit(1) .truncationMode(.middle) Image(systemName: "chevron.right") - .font(.caption.weight(.semibold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(.secondary) } - .font(.subheadline) + .font(OpenClawType.subhead) } } diff --git a/apps/ios/Sources/Design/SettingsProTabSupport.swift b/apps/ios/Sources/Design/SettingsProTabSupport.swift index fa77ee195e00..4b2e7b9ff776 100644 --- a/apps/ios/Sources/Design/SettingsProTabSupport.swift +++ b/apps/ios/Sources/Design/SettingsProTabSupport.swift @@ -36,7 +36,7 @@ struct SettingsApprovalRow: View { var body: some View { HStack(spacing: 10) { Image(systemName: self.item.icon) - .font(.caption.weight(.bold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(.white) .frame(width: 30, height: 30) .background { @@ -45,16 +45,16 @@ struct SettingsApprovalRow: View { } VStack(alignment: .leading, spacing: 2) { Text(self.item.title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .lineLimit(1) Text(self.item.detail) - .font(.caption2.weight(.medium)) + .font(OpenClawType.caption2Medium) .foregroundStyle(.secondary) .lineLimit(1) } Spacer(minLength: 8) Text(self.item.priority) - .font(.caption.weight(.bold)) + .font(OpenClawType.captionSemiBold) .foregroundStyle(self.item.color) .padding(.horizontal, 9) .padding(.vertical, 5) @@ -298,7 +298,7 @@ private struct SettingsGatewayStatesPreview: View { { VStack(alignment: .leading, spacing: 8) { Text(title) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .foregroundStyle(.secondary) content() } @@ -344,11 +344,11 @@ private struct SettingsGatewayStatesPreview: View { private func factRow(_ label: String, value: String) -> some View { HStack { Text(label) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) Spacer(minLength: 8) Text(value) - .font(.caption.weight(.medium)) + .font(OpenClawType.captionMedium) .lineLimit(1) .truncationMode(.middle) } @@ -372,7 +372,7 @@ private struct SettingsGatewayStatesPreview: View { self.previewButton("Connect", systemImage: "link", isBusy: false) } Text("Discovered gateways and manual setup live here when the gateway has not connected yet.") - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) } } diff --git a/apps/ios/Sources/Design/TalkProTab.swift b/apps/ios/Sources/Design/TalkProTab.swift index 381e6a497abf..89f4c3583be4 100644 --- a/apps/ios/Sources/Design/TalkProTab.swift +++ b/apps/ios/Sources/Design/TalkProTab.swift @@ -134,17 +134,17 @@ struct TalkProTab: View { VStack(spacing: 5) { Text(self.state.title) - .font(.title3.weight(.bold)) + .font(OpenClawType.title3) .multilineTextAlignment(.center) Text(self.heroSubtitle) - .font(.subheadline.weight(.medium)) + .font(OpenClawType.subhead) .foregroundStyle(.secondary) .multilineTextAlignment(.center) } Button(action: self.handlePrimaryAction) { Label(self.state.primaryButtonTitle, systemImage: self.state.primaryButtonIcon) - .font(.subheadline.weight(.bold)) + .font(OpenClawType.subheadSemiBold) .frame(maxWidth: .infinity) .frame(height: 50) } @@ -171,7 +171,7 @@ struct TalkProTab: View { accessibilityIdentifier: "talk-background-listening-control") Button(action: self.openVoiceSettings) { Image(systemName: "slider.horizontal.3") - .font(.system(size: 17, weight: .semibold)) + .font(OpenClawType.headline) .frame(width: 44, height: 44) } .buttonBorderShape(.circle) @@ -194,7 +194,7 @@ struct TalkProTab: View { isOn.wrappedValue.toggle() } label: { Image(systemName: systemImage) - .font(.system(size: 17, weight: .semibold)) + .font(OpenClawType.headline) .contentTransition(.symbolEffect(.replace)) .frame(width: 44, height: 44) } @@ -518,7 +518,7 @@ private struct TalkProOrb: View { .frame(width: 92, height: 44) .opacity(self.showsWaveform ? 1 : 0) Image(systemName: self.systemImage) - .font(.system(size: 34, weight: .bold)) + .font(OpenClawType.title1) .foregroundStyle(self.color) .opacity(self.showsWaveform ? 0.20 : 1) } diff --git a/apps/ios/Sources/Design/TalkRuntimeIssueBanner.swift b/apps/ios/Sources/Design/TalkRuntimeIssueBanner.swift index 6fb7485aa3ae..201527b11d09 100644 --- a/apps/ios/Sources/Design/TalkRuntimeIssueBanner.swift +++ b/apps/ios/Sources/Design/TalkRuntimeIssueBanner.swift @@ -43,12 +43,12 @@ struct TalkRuntimeIssueDetailsSheet: View { Section { VStack(alignment: .leading, spacing: 10) { Text(self.issue.fallbackBannerTitle) - .font(.title3.weight(.semibold)) + .font(OpenClawType.title3) Text(self.issue.fallbackBannerMessage) - .font(.body) + .font(OpenClawType.body) .foregroundStyle(.secondary) Text(self.issue.displayMessage) - .font(.footnote.weight(.semibold)) + .font(OpenClawType.footnoteSemiBold) .foregroundStyle(.secondary) } .frame(maxWidth: .infinity, alignment: .leading) @@ -57,7 +57,7 @@ struct TalkRuntimeIssueDetailsSheet: View { Section("Technical details") { Text(verbatim: self.issue.technicalDetails) - .font(.system(.footnote, design: .monospaced)) + .font(OpenClawType.monoFootnote) .foregroundStyle(.secondary) .textSelection(.enabled) Button("Copy diagnostics") { @@ -69,7 +69,7 @@ struct TalkRuntimeIssueDetailsSheet: View { if let copyFeedback { Section { Text(copyFeedback) - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(.secondary) } } diff --git a/apps/ios/Sources/Fonts/DMSans-Italic.ttf b/apps/ios/Sources/Fonts/DMSans-Italic.ttf new file mode 100644 index 000000000000..e592ba02c12e Binary files /dev/null and b/apps/ios/Sources/Fonts/DMSans-Italic.ttf differ diff --git a/apps/ios/Sources/Fonts/DMSans-Light.ttf b/apps/ios/Sources/Fonts/DMSans-Light.ttf new file mode 100644 index 000000000000..2ca205897323 Binary files /dev/null and b/apps/ios/Sources/Fonts/DMSans-Light.ttf differ diff --git a/apps/ios/Sources/Fonts/DMSans-Medium.ttf b/apps/ios/Sources/Fonts/DMSans-Medium.ttf new file mode 100644 index 000000000000..39ddfe4c3612 Binary files /dev/null and b/apps/ios/Sources/Fonts/DMSans-Medium.ttf differ diff --git a/apps/ios/Sources/Fonts/DMSans-Regular.ttf b/apps/ios/Sources/Fonts/DMSans-Regular.ttf new file mode 100644 index 000000000000..9b1d3099a7b4 Binary files /dev/null and b/apps/ios/Sources/Fonts/DMSans-Regular.ttf differ diff --git a/apps/ios/Sources/Fonts/DMSans-SemiBold.ttf b/apps/ios/Sources/Fonts/DMSans-SemiBold.ttf new file mode 100644 index 000000000000..ccb4b63ebc35 Binary files /dev/null and b/apps/ios/Sources/Fonts/DMSans-SemiBold.ttf differ diff --git a/apps/ios/Sources/Fonts/JetBrainsMono-Medium.ttf b/apps/ios/Sources/Fonts/JetBrainsMono-Medium.ttf new file mode 100644 index 000000000000..97671156df25 Binary files /dev/null and b/apps/ios/Sources/Fonts/JetBrainsMono-Medium.ttf differ diff --git a/apps/ios/Sources/Fonts/JetBrainsMono-Regular.ttf b/apps/ios/Sources/Fonts/JetBrainsMono-Regular.ttf new file mode 100644 index 000000000000..dff66cc50702 Binary files /dev/null and b/apps/ios/Sources/Fonts/JetBrainsMono-Regular.ttf differ diff --git a/apps/ios/Sources/Fonts/JetBrainsMono-SemiBold.ttf b/apps/ios/Sources/Fonts/JetBrainsMono-SemiBold.ttf new file mode 100644 index 000000000000..a70e69bd21aa Binary files /dev/null and b/apps/ios/Sources/Fonts/JetBrainsMono-SemiBold.ttf differ diff --git a/apps/ios/Sources/Fonts/LICENSE b/apps/ios/Sources/Fonts/LICENSE new file mode 100644 index 000000000000..d9ddea5f7478 --- /dev/null +++ b/apps/ios/Sources/Fonts/LICENSE @@ -0,0 +1,117 @@ +Font Licenses and Provenance +============================= + +All bundled fonts are licensed under the SIL Open Font License, Version 1.1. +The OFL permits bundling in software provided the copyright notice and license +accompany the distribution. + +Plus Jakarta Sans +----------------- +Copyright 2020 The Plus Jakarta Sans Project Authors + (https://github.com/tokotype/PlusJakartaSans) +License: SIL Open Font License 1.1 +Source: https://github.com/tokotype/PlusJakartaSans +Version: 2.7.1 + +DM Sans +------- +Copyright 2014-2022 Indian Type Foundry +Copyright 2014-2022 Jonny Pinhorn + (https://github.com/googlefonts/dm-fonts) +License: SIL Open Font License 1.1 +Source: https://github.com/googlefonts/dm-fonts + +JetBrains Mono +-------------- +Copyright 2020 The JetBrains Mono Project Authors + (https://github.com/JetBrains/JetBrainsMono) +License: SIL Open Font License 1.1 +Source: https://github.com/JetBrains/JetBrainsMono +Version: 2.304 + +--- + +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/apps/ios/Sources/Fonts/PlusJakartaSans-Bold.ttf b/apps/ios/Sources/Fonts/PlusJakartaSans-Bold.ttf new file mode 100644 index 000000000000..63cb1235b9e5 Binary files /dev/null and b/apps/ios/Sources/Fonts/PlusJakartaSans-Bold.ttf differ diff --git a/apps/ios/Sources/Fonts/PlusJakartaSans-ExtraBold.ttf b/apps/ios/Sources/Fonts/PlusJakartaSans-ExtraBold.ttf new file mode 100644 index 000000000000..bc6f1d582295 Binary files /dev/null and b/apps/ios/Sources/Fonts/PlusJakartaSans-ExtraBold.ttf differ diff --git a/apps/ios/Sources/Fonts/PlusJakartaSans-Light.ttf b/apps/ios/Sources/Fonts/PlusJakartaSans-Light.ttf new file mode 100644 index 000000000000..164085698830 Binary files /dev/null and b/apps/ios/Sources/Fonts/PlusJakartaSans-Light.ttf differ diff --git a/apps/ios/Sources/Fonts/PlusJakartaSans-Medium.ttf b/apps/ios/Sources/Fonts/PlusJakartaSans-Medium.ttf new file mode 100644 index 000000000000..7576ff2609c4 Binary files /dev/null and b/apps/ios/Sources/Fonts/PlusJakartaSans-Medium.ttf differ diff --git a/apps/ios/Sources/Fonts/PlusJakartaSans-Regular.ttf b/apps/ios/Sources/Fonts/PlusJakartaSans-Regular.ttf new file mode 100644 index 000000000000..95fde720f0c3 Binary files /dev/null and b/apps/ios/Sources/Fonts/PlusJakartaSans-Regular.ttf differ diff --git a/apps/ios/Sources/Fonts/PlusJakartaSans-SemiBold.ttf b/apps/ios/Sources/Fonts/PlusJakartaSans-SemiBold.ttf new file mode 100644 index 000000000000..2d4615537c74 Binary files /dev/null and b/apps/ios/Sources/Fonts/PlusJakartaSans-SemiBold.ttf differ diff --git a/apps/ios/Sources/Gateway/ExecApprovalPromptDialog.swift b/apps/ios/Sources/Gateway/ExecApprovalPromptDialog.swift index 92c74c4741f6..a98c89695410 100644 --- a/apps/ios/Sources/Gateway/ExecApprovalPromptDialog.swift +++ b/apps/ios/Sources/Gateway/ExecApprovalPromptDialog.swift @@ -60,17 +60,19 @@ private struct ExecApprovalPromptCard: View { VStack(alignment: .leading, spacing: 14) { VStack(alignment: .leading, spacing: 6) { Text("Exec approval required") - .font(.headline) + .font(OpenClawType.headline) Text("Review this exec request before continuing. Your decision will be sent back to the gateway.") - .font(.subheadline) + .font(OpenClawType.subhead) .foregroundStyle(.secondary) } Text(self.prompt.commandText) - .font(.system(size: 15, weight: .regular, design: .monospaced)) + .font(OpenClawType.mono) .frame(maxWidth: .infinity, alignment: .leading) .padding(10) - .background(.black.opacity(0.14), in: RoundedRectangle(cornerRadius: 12, style: .continuous)) + .background( + .black.opacity(0.14), + in: RoundedRectangle(cornerRadius: OpenClawRadius.md, style: .continuous)) VStack(alignment: .leading, spacing: 8) { if let host = self.normalized(self.prompt.host) { @@ -89,7 +91,7 @@ private struct ExecApprovalPromptCard: View { if let errorText = self.normalized(self.errorText) { Text(errorText) - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(OpenClawBrand.danger) } @@ -98,7 +100,7 @@ private struct ExecApprovalPromptCard: View { ProgressView() .progressViewStyle(.circular) Text("Resolving…") - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(.secondary) } } @@ -181,10 +183,10 @@ private struct ExecApprovalPromptMetadataRow: View { var body: some View { VStack(alignment: .leading, spacing: 2) { Text(self.label) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) Text(self.value) - .font(.footnote) + .font(OpenClawType.footnote) .textSelection(.enabled) } } diff --git a/apps/ios/Sources/Gateway/GatewayDiscoveryDebugLogView.swift b/apps/ios/Sources/Gateway/GatewayDiscoveryDebugLogView.swift index 8307225409ca..18c92fe3cd31 100644 --- a/apps/ios/Sources/Gateway/GatewayDiscoveryDebugLogView.swift +++ b/apps/ios/Sources/Gateway/GatewayDiscoveryDebugLogView.swift @@ -19,10 +19,10 @@ struct GatewayDiscoveryDebugLogView: View { ForEach(self.gatewayController.discoveryDebugLog) { entry in VStack(alignment: .leading, spacing: 2) { Text(Self.formatTime(entry.ts)) - .font(.caption) + .font(OpenClawType.caption) .foregroundStyle(.secondary) Text(entry.message) - .font(.callout) + .font(OpenClawType.callout) .textSelection(.enabled) } .padding(.vertical, 4) diff --git a/apps/ios/Sources/Gateway/GatewayProblemView.swift b/apps/ios/Sources/Gateway/GatewayProblemView.swift index 14cc144e11cb..f9b157d12b59 100644 --- a/apps/ios/Sources/Gateway/GatewayProblemView.swift +++ b/apps/ios/Sources/Gateway/GatewayProblemView.swift @@ -89,12 +89,12 @@ struct GatewayProblemDetailsSheet: View { Section { VStack(alignment: .leading, spacing: 10) { Text(self.problem.title) - .font(.title3.weight(.semibold)) + .font(OpenClawType.title3) Text(self.problem.message) - .font(.body) + .font(OpenClawType.body) .foregroundStyle(.secondary) Text(self.ownerSummary) - .font(.footnote.weight(.semibold)) + .font(OpenClawType.footnoteSemiBold) .foregroundStyle(.secondary) } .frame(maxWidth: .infinity, alignment: .leading) @@ -104,7 +104,7 @@ struct GatewayProblemDetailsSheet: View { if let requestId = self.problem.requestId { Section("Request") { Text(verbatim: requestId) - .font(.system(.body, design: .monospaced)) + .font(OpenClawType.mono) .textSelection(.enabled) Button("Copy request ID") { UIPasteboard.general.string = requestId @@ -116,7 +116,7 @@ struct GatewayProblemDetailsSheet: View { if let actionCommand = self.problem.actionCommand { Section("Gateway command") { Text(verbatim: actionCommand) - .font(.system(.body, design: .monospaced)) + .font(OpenClawType.mono) .textSelection(.enabled) Button("Copy command") { UIPasteboard.general.string = actionCommand @@ -131,7 +131,7 @@ struct GatewayProblemDetailsSheet: View { Label("Open docs", systemImage: "book") } Text(verbatim: docsURL.absoluteString) - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(.secondary) .textSelection(.enabled) } @@ -140,7 +140,7 @@ struct GatewayProblemDetailsSheet: View { if let technicalDetails = self.problem.technicalDetails { Section("Technical details") { Text(verbatim: technicalDetails) - .font(.system(.footnote, design: .monospaced)) + .font(OpenClawType.monoFootnote) .foregroundStyle(.secondary) .textSelection(.enabled) } @@ -149,7 +149,7 @@ struct GatewayProblemDetailsSheet: View { if let copyFeedback { Section { Text(copyFeedback) - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(.secondary) } } diff --git a/apps/ios/Sources/Gateway/GatewayQuickSetupSheet.swift b/apps/ios/Sources/Gateway/GatewayQuickSetupSheet.swift index 229786cabf5a..f3df4bb6cddf 100644 --- a/apps/ios/Sources/Gateway/GatewayQuickSetupSheet.swift +++ b/apps/ios/Sources/Gateway/GatewayQuickSetupSheet.swift @@ -15,7 +15,7 @@ struct GatewayQuickSetupSheet: View { NavigationStack { VStack(alignment: .leading, spacing: 16) { Text("Connect to a Gateway?") - .font(.title2.bold()) + .font(OpenClawType.title2) if let gatewayProblem = self.appModel.lastGatewayProblem { GatewayProblemBanner( @@ -46,7 +46,6 @@ struct GatewayQuickSetupSheet: View { 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: { @@ -60,14 +59,13 @@ struct GatewayQuickSetupSheet: View { Text("Connect") } } - .frame(maxWidth: .infinity) } - .buttonStyle(.borderedProminent) + .openClawPrimaryButton() .disabled(self.connecting) if let connectError { Text(connectError) - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(.secondary) .textSelection(.enabled) } @@ -76,9 +74,8 @@ struct GatewayQuickSetupSheet: View { self.dismiss() } label: { Text("Not now") - .frame(maxWidth: .infinity) } - .buttonStyle(.bordered) + .openClawSecondaryButton() .disabled(self.connecting) self.fullRowToggle("Don’t show this again", isOn: self.$quickSetupDismissed) @@ -173,10 +170,10 @@ private struct GatewayQuickSetupCandidatePanel: View { var body: some View { VStack(alignment: .leading, spacing: 6) { Text(verbatim: self.name) - .font(.system(.headline, design: .monospaced)) + .font(OpenClawType.monoHeadline) .foregroundStyle(.primary) Text(verbatim: self.debugID) - .font(.system(.footnote, design: .monospaced)) + .font(OpenClawType.monoFootnote) .foregroundStyle(.secondary) VStack(alignment: .leading, spacing: 2) { @@ -187,7 +184,7 @@ private struct GatewayQuickSetupCandidatePanel: View { Text(verbatim: "Node: \(self.nodeStatusText)") Text(verbatim: "Operator: \(self.operatorStatusText)") } - .font(.system(.footnote, design: .monospaced)) + .font(OpenClawType.monoFootnote) .foregroundStyle(.secondary) } .frame(maxWidth: Self.readableMonospaceWidth, alignment: .leading) @@ -195,7 +192,7 @@ private struct GatewayQuickSetupCandidatePanel: View { .padding(.horizontal, 16) .frame(maxWidth: .infinity, alignment: .leading) .textSelection(.enabled) - .background(.thinMaterial) - .clipShape(RoundedRectangle(cornerRadius: 14)) + .background(OpenClawBrand.obsidian) + .clipShape(RoundedRectangle(cornerRadius: OpenClawRadius.lg)) } } diff --git a/apps/ios/Sources/Gateway/NotificationPermissionGuidanceDialog.swift b/apps/ios/Sources/Gateway/NotificationPermissionGuidanceDialog.swift index 91e882ff597c..da7e9556fb5e 100644 --- a/apps/ios/Sources/Gateway/NotificationPermissionGuidanceDialog.swift +++ b/apps/ios/Sources/Gateway/NotificationPermissionGuidanceDialog.swift @@ -50,14 +50,14 @@ private struct NotificationPermissionGuidanceCard: View { VStack(alignment: .leading, spacing: 14) { VStack(alignment: .leading, spacing: 6) { Text("Notifications are off") - .font(.headline) + .font(OpenClawType.headline) Text( """ Exec approvals can only be reviewed while OpenClaw is open and connected. Enable Notifications to receive approval notifications while OpenClaw is not open. """) - .font(.subheadline) + .font(OpenClawType.subhead) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } diff --git a/apps/ios/Sources/Info.plist b/apps/ios/Sources/Info.plist index 5cb15a8e57de..3aac2491bf43 100644 --- a/apps/ios/Sources/Info.plist +++ b/apps/ios/Sources/Info.plist @@ -88,6 +88,23 @@ $(OPENCLAW_PUSH_MODE) OpenClawPushRelayBaseURL $(OPENCLAW_PUSH_RELAY_BASE_URL) + UIAppFonts + + PlusJakartaSans-Light.ttf + PlusJakartaSans-Regular.ttf + PlusJakartaSans-Medium.ttf + PlusJakartaSans-SemiBold.ttf + PlusJakartaSans-Bold.ttf + PlusJakartaSans-ExtraBold.ttf + DMSans-Light.ttf + DMSans-Regular.ttf + DMSans-Italic.ttf + DMSans-Medium.ttf + DMSans-SemiBold.ttf + JetBrainsMono-Regular.ttf + JetBrainsMono-Medium.ttf + JetBrainsMono-SemiBold.ttf + UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/apps/ios/Sources/Onboarding/OnboardingWizardSteps.swift b/apps/ios/Sources/Onboarding/OnboardingWizardSteps.swift index e1616fb68285..6cddf755d286 100644 --- a/apps/ios/Sources/Onboarding/OnboardingWizardSteps.swift +++ b/apps/ios/Sources/Onboarding/OnboardingWizardSteps.swift @@ -11,15 +11,16 @@ struct OnboardingIntroStep: View { .padding(.bottom, 18) Text("Welcome to OpenClaw") - .font(.largeTitle.weight(.bold)) + .font(OpenClawType.title1) + .foregroundStyle(OpenClawBrand.textPrimary) .multilineTextAlignment(.center) .padding(.bottom, 10) Text("Turn this device into a secure OpenClaw node for chat, voice, camera, and device tools.") - .font(.subheadline) + .font(OpenClawType.subhead) .foregroundStyle(.secondary) .multilineTextAlignment(.center) - .padding(.horizontal, 32) + .padding(.horizontal, OpenClawSpacing.space8) .padding(.bottom, 24) VStack(alignment: .leading, spacing: 14) { @@ -27,42 +28,43 @@ struct OnboardingIntroStep: View { Label("Choose device permissions", systemImage: "hand.raised") Label("Use OpenClaw from your phone", systemImage: "message.fill") } - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .frame(maxWidth: .infinity, alignment: .leading) .padding(18) .background { - RoundedRectangle(cornerRadius: 20, style: .continuous) - .fill(Color(uiColor: .secondarySystemBackground)) + RoundedRectangle(cornerRadius: OpenClawRadius.xl, style: .continuous) + .fill(OpenClawBrand.slate) } - .padding(.horizontal, 24) + .padding(.horizontal, OpenClawSpacing.space6) .padding(.bottom, 16) HStack(alignment: .top, spacing: 12) { Image(systemName: "exclamationmark.triangle.fill") - .font(.title3.weight(.semibold)) + .font(OpenClawType.title3) .foregroundStyle(OpenClawBrand.warn) .frame(width: 24) .padding(.top, 2) VStack(alignment: .leading, spacing: 6) { Text("Security notice") - .font(.headline) + .font(OpenClawType.headline) + .foregroundStyle(OpenClawBrand.textPrimary) Text( "The connected OpenClaw agent can use device capabilities you enable, " + "such as camera, microphone, photos, contacts, calendar, and location. " + "Continue only if you trust the gateway and agent you connect to.") - .font(.footnote) - .foregroundStyle(.secondary) + .font(OpenClawType.footnote) + .foregroundStyle(OpenClawBrand.textSecondary) .fixedSize(horizontal: false, vertical: true) } } .frame(maxWidth: .infinity, alignment: .leading) .padding(18) .background { - RoundedRectangle(cornerRadius: 20, style: .continuous) - .fill(Color(uiColor: .secondarySystemBackground)) + RoundedRectangle(cornerRadius: OpenClawRadius.xl, style: .continuous) + .fill(OpenClawBrand.slate) } - .padding(.horizontal, 24) + .padding(.horizontal, OpenClawSpacing.space6) Spacer() @@ -70,11 +72,9 @@ struct OnboardingIntroStep: View { self.onContinue() } label: { Text("Continue") - .frame(maxWidth: .infinity) } - .buttonStyle(.borderedProminent) - .controlSize(.large) - .padding(.horizontal, 24) + .openClawPrimaryButton() + .padding(.horizontal, OpenClawSpacing.space6) .padding(.bottom, 48) } } @@ -95,34 +95,35 @@ struct OnboardingWelcomeStep: View { .padding(.bottom, 20) Text("Connect Gateway") - .font(.largeTitle.weight(.bold)) + .font(OpenClawType.title1) + .foregroundStyle(OpenClawBrand.textPrimary) .padding(.bottom, 8) Text("Scan a QR code from your OpenClaw gateway or continue with manual setup.") - .font(.subheadline) + .font(OpenClawType.subhead) .foregroundStyle(.secondary) .multilineTextAlignment(.center) - .padding(.horizontal, 32) + .padding(.horizontal, OpenClawSpacing.space8) VStack(alignment: .leading, spacing: 8) { Text("How to pair") - .font(.headline) + .font(OpenClawType.headline) Text("In your OpenClaw chat, run") - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(.secondary) Text("/pair qr") - .font(.system(.footnote, design: .monospaced).weight(.semibold)) + .font(OpenClawType.monoFootnote) Text("Then scan the QR code here to connect this device.") - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(.secondary) } .frame(maxWidth: .infinity, alignment: .leading) - .padding(16) + .padding(OpenClawSpacing.space4) .background { - RoundedRectangle(cornerRadius: 18, style: .continuous) - .fill(Color(uiColor: .secondarySystemBackground)) + RoundedRectangle(cornerRadius: OpenClawRadius.xl, style: .continuous) + .fill(OpenClawBrand.slate) } - .padding(.horizontal, 24) + .padding(.horizontal, OpenClawSpacing.space6) .padding(.top, 20) Spacer() @@ -132,28 +133,24 @@ struct OnboardingWelcomeStep: View { self.onScanQRCode() } label: { Label("Scan QR Code", systemImage: "qrcode") - .frame(maxWidth: .infinity) } - .buttonStyle(.borderedProminent) - .controlSize(.large) + .openClawPrimaryButton() Button { self.onManualSetup() } label: { Text("Set Up Manually") - .frame(maxWidth: .infinity) } - .buttonStyle(.bordered) - .controlSize(.large) + .openClawSecondaryButton() } - .padding(.horizontal, 24) + .padding(.horizontal, OpenClawSpacing.space6) .padding(.bottom, 12) Text(self.statusLine) - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(.secondary) .multilineTextAlignment(.center) - .padding(.horizontal, 24) + .padding(.horizontal, OpenClawSpacing.space6) .padding(.bottom, 48) } } @@ -170,9 +167,9 @@ struct OnboardingModeRow: View { HStack { VStack(alignment: .leading, spacing: 2) { Text(self.title) - .font(.body.weight(.semibold)) + .font(OpenClawType.headline) Text(self.subtitle) - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(.secondary) } Spacer() diff --git a/apps/ios/Sources/Onboarding/OnboardingWizardView.swift b/apps/ios/Sources/Onboarding/OnboardingWizardView.swift index 7cb8638d738e..d77ffa7a472d 100644 --- a/apps/ios/Sources/Onboarding/OnboardingWizardView.swift +++ b/apps/ios/Sources/Onboarding/OnboardingWizardView.swift @@ -129,9 +129,9 @@ struct OnboardingWizardView: View { ToolbarItem(placement: .principal) { VStack(spacing: 2) { Text(self.step.title) - .font(.headline) + .font(OpenClawType.headline) Text(self.step.manualProgressTitle) - .font(.caption2) + .font(OpenClawType.caption2) .foregroundStyle(.secondary) } } @@ -374,7 +374,7 @@ struct OnboardingWizardView: View { HStack { Text(title) Spacer(minLength: 8) - self.onboardingSwitchIndicator(isOn: isOn.wrappedValue) + OpenClawToggleIndicator(isOn: isOn.wrappedValue) } .contentShape(Rectangle()) } @@ -383,19 +383,6 @@ struct OnboardingWizardView: View { .accessibilityValue(isOn.wrappedValue ? "On" : "Off") } - private func onboardingSwitchIndicator(isOn: Bool) -> some View { - Capsule() - .fill(isOn ? OpenClawBrand.accent : Color.secondary.opacity(0.35)) - .frame(width: 52, height: 32) - .overlay(alignment: isOn ? .trailing : .leading) { - Circle() - .fill(Color.white) - .frame(width: 28, height: 28) - .padding(2) - .shadow(color: Color.black.opacity(0.14), radius: 1, x: 0, y: 1) - } - } - @ViewBuilder private var connectStep: some View { if let selectedMode { @@ -445,7 +432,7 @@ struct OnboardingWizardView: View { Text(gateway.name) if let host = gateway.lanHost ?? gateway.tailnetDns { Text(host) - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(.secondary) } } @@ -517,11 +504,11 @@ struct OnboardingWizardView: View { }) } else if self.issue.needsAuthToken { Text("Gateway rejected credentials. Scan a fresh QR code or update token/password.") - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(.secondary) } else { Text("Auth token looks valid.") - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(.secondary) } } @@ -586,18 +573,19 @@ struct OnboardingWizardView: View { .padding(.bottom, 20) Text("Connected") - .font(.largeTitle.weight(.bold)) + .font(OpenClawType.title1) + .foregroundStyle(OpenClawBrand.textPrimary) .padding(.bottom, 8) let server = self.appModel.gatewayServerName ?? "gateway" Text(server) - .font(.subheadline) + .font(OpenClawType.subhead) .foregroundStyle(.secondary) .padding(.bottom, 4) if let addr = self.appModel.gatewayRemoteAddress { Text(addr) - .font(.subheadline) + .font(OpenClawType.subhead) .foregroundStyle(.secondary) } @@ -607,11 +595,9 @@ struct OnboardingWizardView: View { self.onClose() } label: { Text("Open OpenClaw") - .frame(maxWidth: .infinity) } - .buttonStyle(.borderedProminent) - .controlSize(.large) - .padding(.horizontal, 24) + .openClawPrimaryButton() + .padding(.horizontal, OpenClawSpacing.space6) .padding(.bottom, 48) } } @@ -646,7 +632,7 @@ extension OnboardingWizardView { if let setupCodeStatus, !setupCodeStatus.isEmpty { Text(setupCodeStatus) - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(.secondary) } } header: { diff --git a/apps/ios/Sources/Settings/PrivacyAccessSectionView.swift b/apps/ios/Sources/Settings/PrivacyAccessSectionView.swift index 416a00774833..86f2640dcd65 100644 --- a/apps/ios/Sources/Settings/PrivacyAccessSectionView.swift +++ b/apps/ios/Sources/Settings/PrivacyAccessSectionView.swift @@ -75,17 +75,15 @@ struct PrivacyAccessSectionView: View { HStack { Label(title, systemImage: icon) Spacer() - Text(status) - .font(.footnote.weight(.medium)) - .foregroundStyle(self.statusColor(for: status)) + OpenClawStatusBadge(label: status, tone: self.statusTone(for: status)) .accessibilityIdentifier("privacy-access-\(title)-status") } Text(detail) - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(.secondary) if let actionTitle, let action { Button(actionTitle, action: action) - .font(.footnote) + .font(OpenClawType.footnote) .buttonStyle(.bordered) .accessibilityIdentifier("privacy-access-\(title)-action") } @@ -93,16 +91,16 @@ struct PrivacyAccessSectionView: View { .padding(.vertical, 2) } - private func statusColor(for status: String) -> Color { + private func statusTone(for status: String) -> OpenClawStatusTone { switch status { case "Allowed", "Limited": - OpenClawBrand.ok + .ok case "Not Set": - OpenClawBrand.warn + .warn case "Add-Only": - OpenClawBrand.warn + .warn default: - OpenClawBrand.danger + .danger } } diff --git a/apps/ios/Sources/Status/VoiceWakeToast.swift b/apps/ios/Sources/Status/VoiceWakeToast.swift index 651b458a33fa..c2db80469966 100644 --- a/apps/ios/Sources/Status/VoiceWakeToast.swift +++ b/apps/ios/Sources/Status/VoiceWakeToast.swift @@ -6,11 +6,11 @@ struct VoiceWakeToast: View { var body: some View { HStack(spacing: 10) { Image(systemName: "mic.fill") - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .foregroundStyle(.primary) Text(self.command) - .font(.subheadline.weight(.semibold)) + .font(OpenClawType.subheadSemiBold) .foregroundStyle(.primary) .lineLimit(1) .truncationMode(.tail) diff --git a/apps/ios/Sources/Voice/TalkPermissionPromptView.swift b/apps/ios/Sources/Voice/TalkPermissionPromptView.swift index d701f5fb16a3..31f31c87d163 100644 --- a/apps/ios/Sources/Voice/TalkPermissionPromptView.swift +++ b/apps/ios/Sources/Voice/TalkPermissionPromptView.swift @@ -34,7 +34,7 @@ struct TalkPermissionPromptView: View { VStack(alignment: .leading, spacing: self.style == .sheet ? 16 : 12) { HStack(alignment: .top, spacing: 12) { Image(systemName: self.iconSystemName) - .font(.title3.weight(.semibold)) + .font(OpenClawType.title3) .foregroundStyle(self.requestIsPending ? OpenClawBrand.warn : OpenClawBrand.accent) .frame(width: 28, height: 28) @@ -42,7 +42,7 @@ struct TalkPermissionPromptView: View { Text(self.titleText) .font(self.style == .sheet ? .title3.weight(.semibold) : .headline) Text(self.messageText) - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(.secondary) .fixedSize(horizontal: false, vertical: true) } @@ -50,7 +50,7 @@ struct TalkPermissionPromptView: View { if let failureMessage = self.state.failureMessage { Label(failureMessage, systemImage: "exclamationmark.triangle.fill") - .font(.footnote) + .font(OpenClawType.footnote) .foregroundStyle(OpenClawBrand.danger) .fixedSize(horizontal: false, vertical: true) } @@ -58,7 +58,7 @@ struct TalkPermissionPromptView: View { if let requestId = self.state.requestId { LabeledContent("Request ID") { Text(requestId) - .font(.caption.monospaced()) + .font(OpenClawType.monoSmall) .foregroundStyle(.secondary) .textSelection(.enabled) } @@ -92,13 +92,13 @@ struct TalkPermissionPromptView: View { .padding(self.style == .card || self.style == .sheet ? 16 : 0) .background { if self.style == .card || self.style == .sheet { - RoundedRectangle(cornerRadius: 12, style: .continuous) + RoundedRectangle(cornerRadius: OpenClawRadius.md, style: .continuous) .fill(.thinMaterial) } } .overlay { if self.style == .card || self.style == .sheet { - RoundedRectangle(cornerRadius: 12, style: .continuous) + RoundedRectangle(cornerRadius: OpenClawRadius.md, style: .continuous) .stroke(OpenClawBrand.accent.opacity(0.20), lineWidth: 1) } } diff --git a/apps/ios/Tests/OpenClawTypographyTests.swift b/apps/ios/Tests/OpenClawTypographyTests.swift new file mode 100644 index 000000000000..bd0f8bc98a4c --- /dev/null +++ b/apps/ios/Tests/OpenClawTypographyTests.swift @@ -0,0 +1,27 @@ +import Testing +import UIKit +@testable import OpenClaw + +struct OpenClawTypographyTests { + @Test func `bundled fonts load from app bundle`() { + for name in OpenClawType.registeredPostScriptNames { + #expect(UIFont(name: name, size: 12) != nil, "Missing bundled font: \(name)") + } + } + + @Test func `dynamic type scales display fonts`() { + guard let base = UIFont(name: "PlusJakartaSans-ExtraBold", size: 34) else { + Issue.record("PlusJakartaSans-ExtraBold should be bundled") + return + } + + let defaultTraits = UITraitCollection(preferredContentSizeCategory: .large) + let largeTraits = UITraitCollection(preferredContentSizeCategory: .accessibilityExtraExtraExtraLarge) + let metrics = UIFontMetrics(forTextStyle: .largeTitle) + + let defaultSize = metrics.scaledFont(for: base, compatibleWith: defaultTraits).pointSize + let largeSize = metrics.scaledFont(for: base, compatibleWith: largeTraits).pointSize + + #expect(largeSize > defaultSize) + } +} diff --git a/apps/ios/project.yml b/apps/ios/project.yml index 681482be2be0..899d852f0a9e 100644 --- a/apps/ios/project.yml +++ b/apps/ios/project.yml @@ -63,6 +63,8 @@ targets: - path: Resources/Licenses type: folder buildPhase: resources + - path: Sources/Fonts + buildPhase: resources resources: - path: Resources/Localizable.xcstrings dependencies: @@ -177,6 +179,21 @@ targets: NSSpeechRecognitionUsageDescription: OpenClaw uses on-device speech recognition for talk mode and voice wake. NSSupportsLiveActivities: true ITSAppUsesNonExemptEncryption: false + UIAppFonts: + - PlusJakartaSans-Light.ttf + - PlusJakartaSans-Regular.ttf + - PlusJakartaSans-Medium.ttf + - PlusJakartaSans-SemiBold.ttf + - PlusJakartaSans-Bold.ttf + - PlusJakartaSans-ExtraBold.ttf + - DMSans-Light.ttf + - DMSans-Regular.ttf + - DMSans-Italic.ttf + - DMSans-Medium.ttf + - DMSans-SemiBold.ttf + - JetBrainsMono-Regular.ttf + - JetBrainsMono-Medium.ttf + - JetBrainsMono-SemiBold.ttf OpenClawPushMode: "$(OPENCLAW_PUSH_MODE)" OpenClawPushRelayBaseURL: "$(OPENCLAW_PUSH_RELAY_BASE_URL)" UISupportedInterfaceOrientations: