mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
fix(ios): centralize app accent colors (#94627)
Move iOS accent and status colors through design tokens so raw SwiftUI color literals are blocked outside token definitions. Set the app-wide tint in SwiftUI and UIKit from code, without relying on Assets.xcassets AccentColor.
This commit is contained in:
+17
-1
@@ -2,8 +2,24 @@ parent_config: ../../config/swiftlint.yml
|
||||
|
||||
included:
|
||||
- Sources
|
||||
- ../shared/ClawdisNodeKit/Sources
|
||||
- ShareExtension
|
||||
- ActivityWidget
|
||||
- WatchApp
|
||||
- ../shared/OpenClawKit/Sources/OpenClawChatUI
|
||||
|
||||
excluded:
|
||||
- ../macos
|
||||
|
||||
type_body_length:
|
||||
warning: 900
|
||||
error: 1300
|
||||
|
||||
custom_rules:
|
||||
openclaw_design_colors:
|
||||
name: "OpenClaw design colors"
|
||||
excluded:
|
||||
- Sources/Design/OpenClawBrand.swift
|
||||
- ../shared/OpenClawKit/Sources/OpenClawChatUI/ChatTheme.swift
|
||||
regex: '(Color\.accentColor|(^|[^A-Za-z0-9_])\.accentColor\b|Color\.(red|green|orange|cyan|blue|yellow|purple|pink)\b|\.(foregroundStyle|tint|fill|stroke|strokeBorder|background)\(\s*\.(red|green|orange|cyan|blue|yellow|purple|pink)\b|Color\(red:\s*0\s*/\s*255\.0,\s*green:\s*122\s*/\s*255\.0,\s*blue:\s*255\s*/\s*255\.0\))'
|
||||
message: "Use OpenClawBrand or OpenClawChatTheme design tokens instead of raw accent/status colors."
|
||||
severity: error
|
||||
|
||||
@@ -74,23 +74,30 @@ struct OpenClawLiveActivity: Widget {
|
||||
private func statusIcon(state: OpenClawActivityAttributes.ContentState) -> some View {
|
||||
if state.isConnecting {
|
||||
Image(systemName: "arrow.triangle.2.circlepath")
|
||||
.foregroundStyle(.cyan)
|
||||
.foregroundStyle(OpenClawActivityStyle.info)
|
||||
} else if state.isDisconnected {
|
||||
Image(systemName: "wifi.slash")
|
||||
.foregroundStyle(.red)
|
||||
.foregroundStyle(OpenClawActivityStyle.danger)
|
||||
} else if state.isIdle {
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundStyle(.green)
|
||||
.foregroundStyle(OpenClawActivityStyle.ok)
|
||||
} else {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.foregroundStyle(.orange)
|
||||
.foregroundStyle(OpenClawActivityStyle.warn)
|
||||
}
|
||||
}
|
||||
|
||||
private func dotColor(state: OpenClawActivityAttributes.ContentState) -> Color {
|
||||
if state.isDisconnected { return .red }
|
||||
if state.isConnecting { return .cyan }
|
||||
if state.isIdle { return .green }
|
||||
return .orange
|
||||
if state.isDisconnected { return OpenClawActivityStyle.danger }
|
||||
if state.isConnecting { return OpenClawActivityStyle.info }
|
||||
if state.isIdle { return OpenClawActivityStyle.ok }
|
||||
return OpenClawActivityStyle.warn
|
||||
}
|
||||
}
|
||||
|
||||
private enum OpenClawActivityStyle {
|
||||
static let info = Color(red: 0, green: 122 / 255.0, blue: 1)
|
||||
static let danger = Color(red: 185 / 255.0, green: 28 / 255.0, blue: 28 / 255.0)
|
||||
static let ok = Color(red: 34 / 255.0, green: 197 / 255.0, blue: 94 / 255.0)
|
||||
static let warn = Color(red: 245 / 255.0, green: 158 / 255.0, blue: 11 / 255.0)
|
||||
}
|
||||
|
||||
@@ -506,7 +506,7 @@ extension AgentProTab {
|
||||
|
||||
func skillEditorSwitchIndicator(isOn: Bool) -> some View {
|
||||
Capsule()
|
||||
.fill(isOn ? Color.accentColor : Color.secondary.opacity(0.35))
|
||||
.fill(isOn ? OpenClawBrand.accent : Color.secondary.opacity(0.35))
|
||||
.frame(width: 52, height: 32)
|
||||
.overlay(alignment: isOn ? .trailing : .leading) {
|
||||
Circle()
|
||||
|
||||
@@ -105,7 +105,7 @@ struct AgentProTab: View {
|
||||
var color: Color {
|
||||
switch self {
|
||||
case .online: OpenClawBrand.ok
|
||||
case .ready: Color(red: 0 / 255.0, green: 122 / 255.0, blue: 255 / 255.0)
|
||||
case .ready: OpenClawBrand.info
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +277,7 @@ struct ChatProTab: View {
|
||||
}
|
||||
|
||||
private var chatUserAccent: Color {
|
||||
self.colorScheme == .light ? Color(red: 0 / 255.0, green: 122 / 255.0, blue: 255 / 255.0) : OpenClawBrand.accent
|
||||
self.colorScheme == .light ? OpenClawBrand.info : OpenClawBrand.accent
|
||||
}
|
||||
|
||||
private var activeAgent: AgentSummary? {
|
||||
|
||||
@@ -1036,7 +1036,7 @@ struct IPadSkillProposalRow: View {
|
||||
.padding(.horizontal, 14)
|
||||
.padding(.vertical, 10)
|
||||
.background(
|
||||
self.isSelected ? Color.red.opacity(0.08) : Color.clear,
|
||||
self.isSelected ? OpenClawBrand.danger.opacity(0.08) : Color.clear,
|
||||
in: RoundedRectangle(cornerRadius: 8, style: .continuous))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,11 +47,13 @@ enum AppAppearancePreference: String, CaseIterable, Identifiable {
|
||||
}
|
||||
|
||||
enum OpenClawBrand {
|
||||
static let accent = Color(uiColor: UIColor { traits in
|
||||
static let uiAccent = UIColor { traits in
|
||||
traits.userInterfaceStyle == .dark
|
||||
? UIColor(red: 198 / 255.0, green: 62 / 255.0, blue: 56 / 255.0, alpha: 1)
|
||||
: UIColor(red: 183 / 255.0, green: 56 / 255.0, blue: 51 / 255.0, alpha: 1)
|
||||
})
|
||||
}
|
||||
|
||||
static let accent = Color(uiColor: Self.uiAccent)
|
||||
static let accentHot = Color(uiColor: UIColor { traits in
|
||||
traits.userInterfaceStyle == .dark
|
||||
? UIColor(red: 232 / 255.0, green: 92 / 255.0, blue: 86 / 255.0, alpha: 1)
|
||||
@@ -64,6 +66,7 @@ enum OpenClawBrand {
|
||||
})
|
||||
static let ok = Color(red: 34 / 255.0, green: 197 / 255.0, blue: 94 / 255.0)
|
||||
static let warn = Color(red: 245 / 255.0, green: 158 / 255.0, blue: 11 / 255.0)
|
||||
static let info = Color(red: 0 / 255.0, green: 122 / 255.0, blue: 255 / 255.0)
|
||||
static let graphite = Color(uiColor: UIColor { traits in
|
||||
traits.userInterfaceStyle == .dark
|
||||
? UIColor(red: 20 / 255.0, green: 22 / 255.0, blue: 24 / 255.0, alpha: 1)
|
||||
|
||||
@@ -119,7 +119,7 @@ extension SettingsProTab {
|
||||
self.gatewayActionButton(
|
||||
title: "Diagnose",
|
||||
icon: "cross.case",
|
||||
color: Color(red: 0 / 255.0, green: 122 / 255.0, blue: 255 / 255.0),
|
||||
color: OpenClawBrand.info,
|
||||
isBusy: self.isRefreshingGateway)
|
||||
{
|
||||
Task { await self.runDiagnostics() }
|
||||
@@ -476,7 +476,7 @@ extension SettingsProTab {
|
||||
self.gatewayActionButton(
|
||||
title: "Run Diagnostics",
|
||||
icon: "cross.case",
|
||||
color: Color(red: 0 / 255.0, green: 122 / 255.0, blue: 255 / 255.0),
|
||||
color: OpenClawBrand.info,
|
||||
isBusy: self.isRefreshingGateway)
|
||||
{
|
||||
Task { await self.runDiagnostics() }
|
||||
@@ -1040,7 +1040,7 @@ extension SettingsProTab {
|
||||
|
||||
func settingsSwitchIndicator(isOn: Bool) -> some View {
|
||||
Capsule()
|
||||
.fill(isOn ? Color.accentColor : Color.secondary.opacity(0.35))
|
||||
.fill(isOn ? OpenClawBrand.accent : Color.secondary.opacity(0.35))
|
||||
.frame(width: 52, height: 32)
|
||||
.overlay(alignment: isOn ? .trailing : .leading) {
|
||||
Circle()
|
||||
|
||||
@@ -90,7 +90,7 @@ private struct ExecApprovalPromptCard: View {
|
||||
if let errorText = self.normalized(self.errorText) {
|
||||
Text(errorText)
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.red)
|
||||
.foregroundStyle(OpenClawBrand.danger)
|
||||
}
|
||||
|
||||
if self.isResolving {
|
||||
|
||||
@@ -40,7 +40,7 @@ struct OnboardingIntroStep: View {
|
||||
HStack(alignment: .top, spacing: 12) {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.font(.title3.weight(.semibold))
|
||||
.foregroundStyle(.orange)
|
||||
.foregroundStyle(OpenClawBrand.warn)
|
||||
.frame(width: 24)
|
||||
.padding(.top, 2)
|
||||
|
||||
@@ -177,7 +177,7 @@ struct OnboardingModeRow: View {
|
||||
}
|
||||
Spacer()
|
||||
Image(systemName: self.selected ? "checkmark.circle.fill" : "circle")
|
||||
.foregroundStyle(self.selected ? Color.accentColor : Color.secondary)
|
||||
.foregroundStyle(self.selected ? OpenClawBrand.accent : Color.secondary)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
|
||||
@@ -378,7 +378,7 @@ struct OnboardingWizardView: View {
|
||||
|
||||
private func onboardingSwitchIndicator(isOn: Bool) -> some View {
|
||||
Capsule()
|
||||
.fill(isOn ? Color.accentColor : Color.secondary.opacity(0.35))
|
||||
.fill(isOn ? OpenClawBrand.accent : Color.secondary.opacity(0.35))
|
||||
.frame(width: 52, height: 32)
|
||||
.overlay(alignment: isOn ? .trailing : .leading) {
|
||||
Circle()
|
||||
@@ -575,7 +575,7 @@ struct OnboardingWizardView: View {
|
||||
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.font(.system(size: 64))
|
||||
.foregroundStyle(.green)
|
||||
.foregroundStyle(OpenClawBrand.ok)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
Text("Connected")
|
||||
|
||||
@@ -632,6 +632,7 @@ struct OpenClawApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
RootTabs()
|
||||
.tint(OpenClawBrand.accent)
|
||||
.preferredColorScheme(self.appearancePreference.colorScheme)
|
||||
.environment(self.appModel)
|
||||
.environment(self.appModel.voiceWake)
|
||||
@@ -686,6 +687,7 @@ struct OpenClawApp: App {
|
||||
.flatMap(\.windows)
|
||||
.forEach { window in
|
||||
window.overrideUserInterfaceStyle = style
|
||||
window.tintColor = OpenClawBrand.uiAccent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ struct TalkPermissionPromptView: View {
|
||||
HStack(alignment: .top, spacing: 12) {
|
||||
Image(systemName: self.iconSystemName)
|
||||
.font(.title3.weight(.semibold))
|
||||
.foregroundStyle(self.requestIsPending ? Color.orange : Color.accentColor)
|
||||
.foregroundStyle(self.requestIsPending ? OpenClawBrand.warn : OpenClawBrand.accent)
|
||||
.frame(width: 28, height: 28)
|
||||
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
@@ -51,7 +51,7 @@ struct TalkPermissionPromptView: View {
|
||||
if let failureMessage = self.state.failureMessage {
|
||||
Label(failureMessage, systemImage: "exclamationmark.triangle.fill")
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.red)
|
||||
.foregroundStyle(OpenClawBrand.danger)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ struct TalkPermissionPromptView: View {
|
||||
.overlay {
|
||||
if self.style == .card || self.style == .sheet {
|
||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||
.stroke(Color.accentColor.opacity(0.20), lineWidth: 1)
|
||||
.stroke(OpenClawBrand.accent.opacity(0.20), lineWidth: 1)
|
||||
}
|
||||
}
|
||||
.task(id: self.pollTaskKey) {
|
||||
|
||||
@@ -283,7 +283,7 @@ struct OpenClawChatComposer: View {
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 5)
|
||||
.background(Color.accentColor.opacity(0.08))
|
||||
.background(OpenClawChatTheme.accent.opacity(0.08))
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
}
|
||||
@@ -550,7 +550,7 @@ struct OpenClawChatComposer: View {
|
||||
.frame(width: self.sendButtonSize, height: self.sendButtonSize)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: self.sendButtonCornerRadius, style: .continuous)
|
||||
.fill(Color.red))
|
||||
.fill(OpenClawChatTheme.danger))
|
||||
.contentShape(RoundedRectangle(cornerRadius: self.sendButtonCornerRadius, style: .continuous))
|
||||
.accessibilityLabel("Stop response")
|
||||
.disabled(self.viewModel.isAborting)
|
||||
|
||||
@@ -57,7 +57,7 @@ private struct ChatMarkdownStyle: ViewModifier {
|
||||
}
|
||||
|
||||
private var inlineStyle: InlineStyle {
|
||||
let linkColor: Color = self.context == .user ? self.textColor : .accentColor
|
||||
let linkColor: Color = self.context == .user ? self.textColor : OpenClawChatTheme.accent
|
||||
let codeScale: CGFloat = self.variant == .compact ? 0.85 : 0.9
|
||||
return InlineStyle()
|
||||
.code(.monospaced, .fontScale(codeScale))
|
||||
|
||||
@@ -25,7 +25,7 @@ struct ChatAgentAvatar: View {
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [
|
||||
(self.tint ?? Color.accentColor).opacity(0.95),
|
||||
(self.tint ?? OpenClawChatTheme.accent).opacity(0.95),
|
||||
Color(red: 38 / 255.0, green: 40 / 255.0, blue: 43 / 255.0),
|
||||
],
|
||||
startPoint: .topLeading,
|
||||
@@ -33,7 +33,7 @@ struct ChatAgentAvatar: View {
|
||||
.overlay(
|
||||
Circle()
|
||||
.strokeBorder(Color.white.opacity(0.18), lineWidth: 1))
|
||||
.shadow(color: (self.tint ?? Color.accentColor).opacity(0.18), radius: 8, y: 4)
|
||||
.shadow(color: (self.tint ?? OpenClawChatTheme.accent).opacity(0.18), radius: 8, y: 4)
|
||||
.accessibilityLabel(self.name.map { "\($0) avatar" } ?? "Agent avatar")
|
||||
}
|
||||
|
||||
|
||||
@@ -152,6 +152,18 @@ enum OpenClawChatTheme {
|
||||
#endif
|
||||
}
|
||||
|
||||
static var accent: Color {
|
||||
self.userBubble
|
||||
}
|
||||
|
||||
static var danger: Color {
|
||||
#if os(macOS)
|
||||
Color(nsColor: .systemRed)
|
||||
#else
|
||||
Color(uiColor: .systemRed)
|
||||
#endif
|
||||
}
|
||||
|
||||
static var assistantBubble: Color {
|
||||
#if os(macOS)
|
||||
Color(nsColor: self.assistantBubbleDynamicNSColor)
|
||||
|
||||
@@ -235,7 +235,7 @@ private struct OpenClawChatPreviewTransport: OpenClawChatTransport {
|
||||
showsSessionSwitcher: false,
|
||||
style: .onboarding,
|
||||
markdownVariant: .standard,
|
||||
userAccent: .blue)
|
||||
userAccent: OpenClawChatTheme.accent)
|
||||
}
|
||||
|
||||
private struct OpenClawChatPreview: View {
|
||||
@@ -250,7 +250,7 @@ private struct OpenClawChatPreview: View {
|
||||
showsSessionSwitcher: true,
|
||||
style: .standard,
|
||||
markdownVariant: .standard,
|
||||
userAccent: .blue,
|
||||
userAccent: OpenClawChatTheme.accent,
|
||||
showsAssistantTrace: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,7 +372,7 @@ public struct OpenClawChatView: View {
|
||||
systemImage: "bubble.left.and.bubble.right.fill",
|
||||
title: self.emptyStateTitle,
|
||||
message: self.emptyStateMessage,
|
||||
tint: .accentColor,
|
||||
tint: OpenClawChatTheme.accent,
|
||||
actionTitle: nil,
|
||||
action: nil)
|
||||
.padding(.horizontal, 24)
|
||||
|
||||
Reference in New Issue
Block a user