Skip to content

Commit 2db5bd3

Browse files
authored
feat(ios): refresh onboarding setup flow (#98868)
1 parent a1f4aa4 commit 2db5bd3

17 files changed

Lines changed: 1962 additions & 949 deletions

apps/.i18n/native-source.json

Lines changed: 308 additions & 228 deletions
Large diffs are not rendered by default.

apps/ios/Resources/Localizable.xcstrings

Lines changed: 134 additions & 134 deletions
Large diffs are not rendered by default.

apps/ios/Sources/Design/OpenClawBrand.swift

Lines changed: 312 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Observation
2+
import OpenClawChatUI
23
import SwiftUI
34

45
enum AppAppearancePreference: String, CaseIterable, Identifiable {
@@ -79,11 +80,9 @@ enum OpenClawBrand {
7980
static let uiAccentForeground = adaptiveUIColor(light: (183, 56, 51), dark: (255, 107, 102))
8081
static let uiAccentHot = adaptiveUIColor(light: (204, 75, 69), dark: (232, 92, 86))
8182
static let uiAccentHotForeground = adaptiveUIColor(light: (166, 55, 50), dark: (255, 123, 115))
82-
static let uiAccentPressed = adaptiveUIColor(light: (148, 40, 36), dark: (148, 40, 36))
8383
static let uiTeal = adaptiveUIColor(light: (0, 196, 176), dark: (0, 196, 176))
8484
static let uiVoid = adaptiveUIColor(light: (246, 247, 249), dark: (11, 12, 17))
8585
static let uiObsidian = adaptiveUIColor(light: (255, 255, 255), dark: (19, 21, 28))
86-
static let uiTextPrimary = adaptiveUIColor(light: (11, 12, 17), dark: (242, 239, 232))
8786
static let uiTextSecondary = adaptiveUIColor(light: (90, 94, 110), dark: (168, 170, 191))
8887
static let uiOK = adaptiveUIColor(light: (19, 122, 62), dark: (48, 209, 88))
8988
static let uiWarn = adaptiveUIColor(light: (154, 87, 0), dark: (255, 214, 10))
@@ -94,18 +93,41 @@ enum OpenClawBrand {
9493
static let accentForeground = Color(uiColor: Self.uiAccentForeground)
9594
static let accentHot = Color(uiColor: Self.uiAccentHot)
9695
static let accentHotForeground = Color(uiColor: Self.uiAccentHotForeground)
97-
static let accentPressed = Color(uiColor: Self.uiAccentPressed)
9896
static let teal = Color(uiColor: Self.uiTeal)
9997
static let void = Color(uiColor: Self.uiVoid)
10098
static let obsidian = Color(uiColor: Self.uiObsidian)
101-
static let textPrimary = Color(uiColor: Self.uiTextPrimary)
10299
static let textSecondary = Color(uiColor: Self.uiTextSecondary)
103100
static let danger = Color(uiColor: Self.uiDanger)
104101
static let ok = Color(uiColor: Self.uiOK)
105102
static let warn = Color(uiColor: Self.uiWarn)
106103
static let info = Color(uiColor: Self.uiInfo)
107104
static let graphite = void
108105
static let graphiteElevated = obsidian
106+
static let activationCanvas = Color(uiColor: adaptiveUIColor(light: (255, 255, 255), dark: (18, 14, 15)))
107+
static let activationSurface = Color(uiColor: adaptiveUIColor(light: (255, 253, 252), dark: (33, 29, 30)))
108+
static let activationSecondaryActionTop = Color(uiColor: adaptiveUIColor(
109+
light: (255, 255, 255),
110+
dark: (36, 32, 33)))
111+
static let activationSecondaryActionBottom = Color(uiColor: adaptiveUIColor(
112+
light: (248, 248, 250),
113+
dark: (30, 27, 28)))
114+
static let activationInsetSurface = Color(uiColor: adaptiveUIColor(light: (246, 241, 238), dark: (44, 37, 38)))
115+
static let activationNeutralSurface = Color(uiColor: adaptiveUIColor(light: (242, 242, 247), dark: (34, 34, 37)))
116+
static let activationNeutralInsetSurface = Color(uiColor: adaptiveUIColor(
117+
light: (247, 247, 249),
118+
dark: (42, 42, 45)))
119+
static let activationNeutralStroke = Color(uiColor: adaptiveUIColor(
120+
light: (0, 0, 0),
121+
dark: (255, 255, 255)).withAlphaComponent(0.08))
122+
static let activationNeutralDivider = Color(uiColor: adaptiveUIColor(
123+
light: (0, 0, 0),
124+
dark: (255, 255, 255)).withAlphaComponent(0.09))
125+
static let activationPrimaryAction = Color(uiColor: adaptiveUIColor(light: (209, 54, 51), dark: (238, 82, 76)))
126+
static let activationPrimaryActionText = Color.white
127+
static let activationHairline = Color(uiColor: adaptiveUIColor(
128+
light: (136, 44, 40),
129+
dark: (255, 210, 205)).withAlphaComponent(0.13))
130+
static let activationGlow = Color(uiColor: adaptiveUIColor(light: (228, 78, 67), dark: (255, 111, 96)))
109131

110132
static var sheetBackground: LinearGradient {
111133
LinearGradient(
@@ -118,6 +140,59 @@ enum OpenClawBrand {
118140
endPoint: .bottomTrailing)
119141
}
120142

143+
static var activationCanvasGradient: LinearGradient {
144+
LinearGradient(
145+
colors: [
146+
activationCanvas,
147+
activationCanvas,
148+
],
149+
startPoint: .topLeading,
150+
endPoint: .bottomTrailing)
151+
}
152+
153+
static var activationInsetGradient: LinearGradient {
154+
LinearGradient(
155+
colors: [
156+
activationInsetSurface.opacity(0.94),
157+
activationInsetSurface,
158+
activationSurface.opacity(0.36),
159+
],
160+
startPoint: .top,
161+
endPoint: .bottom)
162+
}
163+
164+
static var activationNeutralGradient: LinearGradient {
165+
LinearGradient(
166+
colors: [
167+
activationNeutralInsetSurface,
168+
activationNeutralSurface,
169+
],
170+
startPoint: .top,
171+
endPoint: .bottom)
172+
}
173+
174+
static var activationPrimaryGradient: LinearGradient {
175+
LinearGradient(
176+
colors: [
177+
Color(red: 1.0, green: 0.42, blue: 0.34),
178+
activationGlow,
179+
activationPrimaryAction,
180+
Color(red: 0.66, green: 0.12, blue: 0.12),
181+
],
182+
startPoint: .topLeading,
183+
endPoint: .bottomTrailing)
184+
}
185+
186+
static var activationDisabledGradient: LinearGradient {
187+
LinearGradient(
188+
colors: [
189+
activationNeutralInsetSurface,
190+
activationNeutralSurface.opacity(0.92),
191+
],
192+
startPoint: .top,
193+
endPoint: .bottom)
194+
}
195+
121196
private static func adaptiveUIColor(
122197
light: (red: CGFloat, green: CGFloat, blue: CGFloat),
123198
dark: (red: CGFloat, green: CGFloat, blue: CGFloat)) -> UIColor
@@ -133,6 +208,21 @@ enum OpenClawBrand {
133208
}
134209
}
135210

211+
struct OpenClawActivationGlyph: View {
212+
let size: CGFloat
213+
214+
var body: some View {
215+
OpenClawMascotView(floats: false)
216+
.frame(width: self.size, height: self.size)
217+
.shadow(
218+
color: OpenClawBrand.activationGlow.opacity(0.18),
219+
radius: self.size * 0.12,
220+
x: 0,
221+
y: self.size * 0.05)
222+
.accessibilityHidden(true)
223+
}
224+
}
225+
136226
extension View {
137227
func openClawSheetChrome() -> some View {
138228
self
@@ -142,4 +232,222 @@ extension View {
142232
.ignoresSafeArea()
143233
}
144234
}
235+
236+
func openClawCraftSurface(cornerRadius: CGFloat = 24, shadow: Bool = true) -> some View {
237+
self.modifier(OpenClawCraftSurfaceModifier(cornerRadius: cornerRadius, shadow: shadow))
238+
}
239+
}
240+
241+
private struct OpenClawCraftSurfaceModifier: ViewModifier {
242+
let cornerRadius: CGFloat
243+
let shadow: Bool
244+
245+
func body(content: Content) -> some View {
246+
content
247+
.background {
248+
RoundedRectangle(cornerRadius: self.cornerRadius, style: .continuous)
249+
.fill(OpenClawBrand.activationSurface)
250+
.shadow(
251+
color: self.shadow ? Color.black.opacity(0.07) : .clear,
252+
radius: 16,
253+
x: 0,
254+
y: 8)
255+
}
256+
.overlay(alignment: .top) {
257+
RoundedRectangle(cornerRadius: self.cornerRadius, style: .continuous)
258+
.stroke(Color.white.opacity(0.36), lineWidth: 0.5)
259+
.blendMode(.plusLighter)
260+
}
261+
.overlay {
262+
RoundedRectangle(cornerRadius: self.cornerRadius, style: .continuous)
263+
.stroke(OpenClawBrand.activationHairline, lineWidth: 0.5)
264+
}
265+
}
266+
}
267+
268+
struct OpenClawPrimaryActionButtonStyle: ButtonStyle {
269+
@Environment(\.isEnabled) private var isEnabled
270+
var height: CGFloat = 54
271+
var cornerRadius: CGFloat = 18
272+
273+
private var resolvedCornerRadius: CGFloat {
274+
self.height / 2
275+
}
276+
277+
func makeBody(configuration: Configuration) -> some View {
278+
configuration.label
279+
.font(OpenClawType.subheadSemiBold)
280+
.foregroundStyle(self.isEnabled ? OpenClawBrand.activationPrimaryActionText : Color.secondary)
281+
.tint(self.isEnabled ? OpenClawBrand.activationPrimaryActionText : Color.secondary)
282+
.frame(maxWidth: .infinity)
283+
.frame(height: self.height)
284+
.background {
285+
RoundedRectangle(cornerRadius: self.resolvedCornerRadius, style: .continuous)
286+
.fill(self.isEnabled ? Self.primaryFill : OpenClawBrand.activationDisabledGradient)
287+
.shadow(
288+
color: self.isEnabled ? OpenClawBrand.activationPrimaryAction.opacity(0.08) : .clear,
289+
radius: 1,
290+
x: 0,
291+
y: 1)
292+
.shadow(
293+
color: self.isEnabled ? OpenClawBrand.activationPrimaryAction.opacity(0.08) : .clear,
294+
radius: 2,
295+
x: 0,
296+
y: 2)
297+
}
298+
.overlay(alignment: .top) {
299+
RoundedRectangle(cornerRadius: self.resolvedCornerRadius, style: .continuous)
300+
.fill(
301+
LinearGradient(
302+
colors: [
303+
Color.white.opacity(self.isEnabled ? 0.14 : 0.06),
304+
Color.white.opacity(0),
305+
],
306+
startPoint: .top,
307+
endPoint: .center))
308+
.frame(height: self.height * 0.48)
309+
.allowsHitTesting(false)
310+
}
311+
.overlay(alignment: .bottom) {
312+
RoundedRectangle(cornerRadius: self.resolvedCornerRadius, style: .continuous)
313+
.fill(
314+
LinearGradient(
315+
colors: [
316+
Color.white.opacity(0),
317+
Color.white.opacity(self.isEnabled ? 0.08 : 0.03),
318+
],
319+
startPoint: .top,
320+
endPoint: .bottom))
321+
.frame(height: self.height * 0.34)
322+
.allowsHitTesting(false)
323+
}
324+
.overlay {
325+
RoundedRectangle(cornerRadius: self.resolvedCornerRadius, style: .continuous)
326+
.stroke(
327+
self.isEnabled
328+
? OpenClawBrand.activationPrimaryAction.opacity(0.72)
329+
: OpenClawBrand.activationNeutralStroke,
330+
lineWidth: 0.75)
331+
}
332+
.scaleEffect(configuration.isPressed && self.isEnabled ? 0.98 : 1)
333+
.animation(.smooth(duration: 0.14), value: configuration.isPressed)
334+
}
335+
336+
private static var primaryFill: LinearGradient {
337+
LinearGradient(
338+
colors: [
339+
Color(red: 0.93, green: 0.27, blue: 0.25),
340+
Color(red: 0.91, green: 0.25, blue: 0.24),
341+
],
342+
startPoint: .top,
343+
endPoint: .bottom)
344+
}
345+
}
346+
347+
struct OpenClawSecondaryActionButtonStyle: ButtonStyle {
348+
@Environment(\.isEnabled) private var isEnabled
349+
var height: CGFloat = 50
350+
var cornerRadius: CGFloat = 18
351+
var shadowOpacity: Double = 0.035
352+
353+
private var resolvedCornerRadius: CGFloat {
354+
self.height / 2
355+
}
356+
357+
func makeBody(configuration: Configuration) -> some View {
358+
configuration.label
359+
.font(OpenClawType.subheadSemiBold)
360+
.foregroundStyle(self.isEnabled ? OpenClawBrand.activationPrimaryAction : .secondary)
361+
.frame(maxWidth: .infinity)
362+
.frame(height: self.height)
363+
.background {
364+
RoundedRectangle(cornerRadius: self.resolvedCornerRadius, style: .continuous)
365+
.fill(Self.secondaryFill)
366+
.shadow(
367+
color: self.isEnabled ? Color.black.opacity(self.shadowOpacity) : .clear,
368+
radius: 1,
369+
x: 0,
370+
y: 1)
371+
.shadow(
372+
color: self.isEnabled ? Color.black.opacity(self.shadowOpacity) : .clear,
373+
radius: 2,
374+
x: 0,
375+
y: 2)
376+
}
377+
.overlay(alignment: .top) {
378+
RoundedRectangle(cornerRadius: self.resolvedCornerRadius, style: .continuous)
379+
.fill(
380+
LinearGradient(
381+
colors: [
382+
Color.white.opacity(0.42),
383+
Color.white.opacity(0),
384+
],
385+
startPoint: .top,
386+
endPoint: .center))
387+
.frame(height: self.height * 0.48)
388+
.allowsHitTesting(false)
389+
}
390+
.overlay {
391+
RoundedRectangle(cornerRadius: self.resolvedCornerRadius, style: .continuous)
392+
.stroke(OpenClawBrand.activationHairline, lineWidth: 0.75)
393+
}
394+
.scaleEffect(configuration.isPressed && self.isEnabled ? 0.98 : 1)
395+
.animation(.smooth(duration: 0.14), value: configuration.isPressed)
396+
}
397+
398+
private static var secondaryFill: LinearGradient {
399+
LinearGradient(
400+
colors: [
401+
OpenClawBrand.activationSecondaryActionTop,
402+
OpenClawBrand.activationSecondaryActionBottom,
403+
],
404+
startPoint: .top,
405+
endPoint: .bottom)
406+
}
407+
}
408+
409+
struct OpenClawCloseButtonStyle: ButtonStyle {
410+
@Environment(\.isEnabled) private var isEnabled
411+
var minWidth: CGFloat = 36
412+
var height: CGFloat = 36
413+
414+
func makeBody(configuration: Configuration) -> some View {
415+
configuration.label
416+
.font(OpenClawType.subheadSemiBold)
417+
.foregroundStyle(
418+
self.isEnabled
419+
? OpenClawBrand.activationPrimaryAction
420+
: Color.secondary)
421+
.fixedSize(horizontal: true, vertical: false)
422+
.frame(minWidth: self.minWidth)
423+
.frame(height: self.height)
424+
.padding(.horizontal, 7)
425+
.background {
426+
Capsule(style: .continuous)
427+
.fill(OpenClawBrand.activationNeutralGradient)
428+
.shadow(
429+
color: self.isEnabled ? Color.black.opacity(0.045) : .clear,
430+
radius: 1,
431+
x: 0,
432+
y: 1)
433+
.shadow(
434+
color: self.isEnabled ? Color.black.opacity(0.03) : .clear,
435+
radius: 4,
436+
x: 0,
437+
y: 2)
438+
}
439+
.overlay(alignment: .top) {
440+
Capsule(style: .continuous)
441+
.stroke(Color.white.opacity(0.55), lineWidth: 0.5)
442+
.blendMode(.plusLighter)
443+
}
444+
.overlay {
445+
Capsule(style: .continuous)
446+
.stroke(OpenClawBrand.activationNeutralStroke, lineWidth: 0.6)
447+
}
448+
.contentShape(Capsule(style: .continuous))
449+
.opacity(configuration.isPressed && self.isEnabled ? 0.66 : 1)
450+
.scaleEffect(configuration.isPressed && self.isEnabled ? 0.98 : 1)
451+
.animation(.smooth(duration: 0.14), value: configuration.isPressed)
452+
}
145453
}

0 commit comments

Comments
 (0)