Add onboarding, auth flow, design system and project structure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 11:35:29 +05:00
parent 2a3e57c2fd
commit 7ae8da53f0
26 changed files with 67032 additions and 12 deletions

View File

@@ -0,0 +1,111 @@
import SwiftUI
// MARK: - Rosetta Color Tokens
enum RosettaColors {
// MARK: Brand
static let primaryBlue = Color(hex: 0x248AE6)
static let primaryBlueDark = Color(hex: 0x238BE6)
static let primaryBluePressed = Color(hex: 0x2B7CD3)
static let primaryBlueDisabled = Color(hex: 0x9BCDFF)
// MARK: Semantic
static let error = Color(hex: 0xFF3B30)
static let success = Color(hex: 0x34C759)
static let warning = Color(hex: 0xFF9500)
static let accent = Color(hex: 0xE91E63)
static let online = Color(hex: 0x34C759)
// MARK: Auth Backgrounds
static let authBackground = Color(hex: 0x1B1B1B)
static let authSurface = Color(hex: 0x2A2A2A)
// MARK: Shared Neutral
static let secondaryText = Color(hex: 0x8E8E93)
static let tertiaryText = Color(hex: 0x666666)
static let numberGray = Color(hex: 0x888888)
static let hintText = Color(hex: 0x555555)
static let subtleBorder = Color.white.opacity(0.15)
static let cardFill = Color.white.opacity(0.06)
// MARK: Light Theme
enum Light {
static let background = Color.white
static let backgroundSecondary = Color(hex: 0xF2F3F5)
static let surface = Color(hex: 0xF5F5F5)
static let text = Color.black
static let textSecondary = Color(hex: 0x666666)
static let textTertiary = Color(hex: 0x999999)
static let border = Color(hex: 0xE0E0E0)
static let divider = Color(hex: 0xEEEEEE)
static let messageBubble = Color(hex: 0xF5F5F5)
static let messageBubbleOwn = Color(hex: 0xDCF8C6)
static let inputBackground = Color(hex: 0xF2F3F5)
}
// MARK: Dark Theme
enum Dark {
static let background = Color(hex: 0x1E1E1E)
static let backgroundSecondary = Color(hex: 0x2A2A2A)
static let surface = Color(hex: 0x242424)
static let text = Color.white
static let textSecondary = Color(hex: 0x8E8E93)
static let textTertiary = Color(hex: 0x666666)
static let border = Color(hex: 0x2E2E2E)
static let divider = Color(hex: 0x333333)
static let messageBubble = Color(hex: 0x2A2A2A)
static let messageBubbleOwn = Color(hex: 0x263341)
static let inputBackground = Color(hex: 0x2A2A2A)
}
// MARK: Seed Word Colors (12 unique, matching Android)
static let seedWordColors: [Color] = [
Color(hex: 0x5E9FFF),
Color(hex: 0xFF7EB3),
Color(hex: 0x7B68EE),
Color(hex: 0x50C878),
Color(hex: 0xFF6B6B),
Color(hex: 0x4ECDC4),
Color(hex: 0xFFB347),
Color(hex: 0xBA55D3),
Color(hex: 0x87CEEB),
Color(hex: 0xDDA0DD),
Color(hex: 0x98D8C8),
Color(hex: 0xF7DC6F),
]
// MARK: Avatar Palette
static let avatarColors: [(background: Color, text: Color)] = [
(Color(hex: 0xFF6B6B), .white),
(Color(hex: 0x4ECDC4), .white),
(Color(hex: 0x45B7D1), .white),
(Color(hex: 0xF7B731), .white),
(Color(hex: 0x5F27CD), .white),
(Color(hex: 0x00D2D3), .white),
(Color(hex: 0xFF9FF3), .white),
(Color(hex: 0x54A0FF), .white),
]
}
// MARK: - Color Hex Initializer
extension Color {
init(hex: UInt, alpha: Double = 1.0) {
self.init(
.sRGB,
red: Double((hex >> 16) & 0xFF) / 255.0,
green: Double((hex >> 8) & 0xFF) / 255.0,
blue: Double(hex & 0xFF) / 255.0,
opacity: alpha
)
}
}

View File

@@ -0,0 +1,39 @@
import SwiftUI
struct AuthNavigationBar: View {
let title: String?
let onBack: () -> Void
init(title: String? = nil, onBack: @escaping () -> Void) {
self.title = title
self.onBack = onBack
}
var body: some View {
HStack {
Button(action: onBack) {
Image(systemName: "chevron.left")
.font(.system(size: 18, weight: .semibold))
.foregroundStyle(.white)
.frame(width: 44, height: 44)
.contentShape(Rectangle())
}
.accessibilityLabel("Back")
if let title {
Text(title)
.font(.system(size: 18, weight: .semibold))
.foregroundStyle(.white)
}
Spacer()
}
.padding(.horizontal, 8)
.padding(.vertical, 4)
}
}
#Preview {
AuthNavigationBar(title: "Set Password", onBack: {})
.background(RosettaColors.authBackground)
}

View File

@@ -0,0 +1,115 @@
import SwiftUI
// MARK: - Primary Button (Liquid Glass)
struct RosettaPrimaryButtonStyle: ButtonStyle {
var isEnabled: Bool = true
func makeBody(configuration: Configuration) -> some View {
Group {
if #available(iOS 26, *) {
configuration.label
.background {
Capsule().fill(RosettaColors.primaryBlue.opacity(configuration.isPressed ? 0.7 : 1.0))
}
.glassEffect(.regular, in: Capsule())
} else {
configuration.label
.background { glassBackground(isPressed: configuration.isPressed) }
.clipShape(Capsule())
}
}
.opacity(isEnabled ? 1.0 : 0.5)
.scaleEffect(configuration.isPressed ? 0.97 : 1.0)
.animation(.easeOut(duration: 0.15), value: configuration.isPressed)
.allowsHitTesting(isEnabled)
}
private func glassBackground(isPressed: Bool) -> some View {
Capsule()
.fill(RosettaColors.primaryBlue.opacity(isPressed ? 0.7 : 1.0))
.overlay {
Capsule()
.fill(
LinearGradient(
colors: [
Color.white.opacity(0.18),
Color.clear,
Color.black.opacity(0.08),
],
startPoint: .top,
endPoint: .bottom
)
)
}
}
}
// MARK: - Shimmer Overlay
struct ShimmerModifier: ViewModifier {
@State private var phase: CGFloat = -1.0
func body(content: Content) -> some View {
content
.overlay {
GeometryReader { geometry in
let width = geometry.size.width * 0.6
LinearGradient(
colors: [
Color.white.opacity(0),
Color.white.opacity(0.25),
Color.white.opacity(0),
],
startPoint: .leading,
endPoint: .trailing
)
.frame(width: width)
.offset(x: phase * (geometry.size.width + width))
.clipShape(Capsule())
}
}
.onAppear {
withAnimation(
.linear(duration: 2.0)
.repeatForever(autoreverses: false)
) {
phase = 1.0
}
}
}
}
extension View {
func shimmer() -> some View {
modifier(ShimmerModifier())
}
}
// MARK: - Stagger Animation Helper
struct StaggeredAppearance: ViewModifier {
let index: Int
let baseDelay: Double
let stagger: Double
@State private var isVisible = false
func body(content: Content) -> some View {
content
.opacity(isVisible ? 1.0 : 0.0)
.offset(y: isVisible ? 0 : 12)
.onAppear {
let delay = baseDelay + Double(index) * stagger
withAnimation(.easeOut(duration: 0.4).delay(delay)) {
isVisible = true
}
}
}
}
extension View {
func staggeredAppearance(index: Int, baseDelay: Double = 0.2, stagger: Double = 0.05) -> some View {
modifier(StaggeredAppearance(index: index, baseDelay: baseDelay, stagger: stagger))
}
}

View File

@@ -0,0 +1,34 @@
import SwiftUI
struct GlassCard<Content: View>: View {
let cornerRadius: CGFloat
let fillOpacity: Double
let content: () -> Content
init(
cornerRadius: CGFloat = 12,
fillOpacity: Double = 0.06,
@ViewBuilder content: @escaping () -> Content
) {
self.cornerRadius = cornerRadius
self.fillOpacity = fillOpacity
self.content = content
}
var body: some View {
if #available(iOS 26, *) {
content()
.glassEffect(.regular, in: RoundedRectangle(cornerRadius: cornerRadius))
} else {
content()
.background {
RoundedRectangle(cornerRadius: cornerRadius)
.fill(Color.white.opacity(fillOpacity))
.overlay {
RoundedRectangle(cornerRadius: cornerRadius)
.stroke(Color.white.opacity(0.08), lineWidth: 0.5)
}
}
}
}
}

View File

@@ -0,0 +1,50 @@
import SwiftUI
import Lottie
struct LottieView: UIViewRepresentable {
let animationName: String
var loopMode: LottieLoopMode = .playOnce
var animationSpeed: CGFloat = 1.5
var isPlaying: Bool = true
func makeUIView(context: Context) -> LottieAnimationView {
let animationView = LottieAnimationView(name: animationName)
animationView.contentMode = .scaleAspectFit
animationView.loopMode = loopMode
animationView.animationSpeed = animationSpeed
animationView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
animationView.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
if isPlaying {
animationView.play()
}
return animationView
}
func updateUIView(_ uiView: LottieAnimationView, context: Context) {
uiView.loopMode = loopMode
uiView.animationSpeed = animationSpeed
if isPlaying && !uiView.isAnimationPlaying {
uiView.play()
} else if !isPlaying {
uiView.stop()
}
}
}
// MARK: - Crossfading Lottie Container
struct CrossfadingLottieView: View {
let animationName: String
let animationID: Int
var body: some View {
LottieView(
animationName: animationName,
loopMode: .playOnce,
animationSpeed: 1.5
)
.id(animationID)
.transition(.opacity.animation(.easeInOut(duration: 0.4)))
}
}

View File

@@ -0,0 +1,28 @@
import SwiftUI
// MARK: - Rosetta Typography
enum RosettaFont {
static let headlineLarge = Font.system(size: 20, weight: .bold)
static let titleLarge = Font.system(size: 17, weight: .medium)
static let bodyLarge = Font.system(size: 16, weight: .regular)
static let bodyMedium = Font.system(size: 16, weight: .regular)
static let bodySmall = Font.system(size: 13, weight: .regular)
static let labelLarge = Font.system(size: 14, weight: .medium)
static let labelMedium = Font.system(size: 14, weight: .regular)
static let labelSmall = Font.system(size: 13, weight: .regular)
// MARK: Onboarding Specific
static let onboardingTitle = Font.system(size: 30, weight: .bold)
static let onboardingBody = Font.system(size: 17, weight: .regular)
static let onboardingBodyBold = Font.system(size: 17, weight: .semibold)
// MARK: Auth Specific
static let authTitle = Font.system(size: 24, weight: .bold)
static let authSubtitle = Font.system(size: 15, weight: .regular)
static let seedWord = Font.system(size: 17, weight: .medium, design: .monospaced)
static let seedWordNumber = Font.system(size: 14, weight: .regular)
}