Files
Reflect/Shared/Onboarding/views/OnboardingStyle.swift
Trey t 0442eab1f8 Rebrand entire project from Feels to Reflect
Complete rename across all bundle IDs, App Groups, CloudKit containers,
StoreKit product IDs, data store filenames, URL schemes, logger subsystems,
Swift identifiers, user-facing strings (7 languages), file names, directory
names, Xcode project, schemes, assets, and documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 11:47:16 -06:00

186 lines
6.6 KiB
Swift

//
// OnboardingStyle.swift
// Reflect
//
// Created by Claude Code on 12/10/24.
//
import SwiftUI
struct OnboardingStyle: View {
@ObservedObject var onboardingData: OnboardingData
@State private var selectedTheme: AppTheme = .celestial
var body: some View {
ScrollView(showsIndicators: false) {
VStack(spacing: 0) {
// Icon
ZStack {
Circle()
.fill(.white.opacity(0.15))
.frame(width: 100, height: 100)
Text(selectedTheme.emoji)
.font(.system(size: 44))
}
.padding(.top, 40)
.padding(.bottom, 20)
// Title
Text("Choose your vibe")
.font(.title.weight(.bold))
.foregroundColor(.white)
.shadow(color: .black.opacity(0.2), radius: 4, x: 0, y: 2)
.padding(.bottom, 8)
// Subtitle
Text("Each theme sets your colors, icons, and layouts")
.font(.body.weight(.medium))
.foregroundColor(.white.opacity(0.85))
.multilineTextAlignment(.center)
.padding(.horizontal, 32)
.padding(.bottom, 24)
// Theme Grid
LazyVGrid(columns: [
GridItem(.flexible(), spacing: 12),
GridItem(.flexible(), spacing: 12)
], spacing: 14) {
ForEach(AppTheme.allCases) { theme in
OnboardingThemeCard(
theme: theme,
isSelected: selectedTheme == theme,
action: {
let impactMed = UIImpactFeedbackGenerator(style: .medium)
impactMed.impactOccurred()
withAnimation(.easeInOut(duration: 0.3)) {
selectedTheme = theme
}
// Apply the theme immediately
theme.apply()
}
)
}
}
.padding(.horizontal, 20)
// Hint
HStack(spacing: 8) {
Image(systemName: "arrow.left.arrow.right")
.font(.subheadline)
Text("You can change this anytime in Customize")
.font(.caption.weight(.medium))
}
.foregroundColor(.white.opacity(0.7))
.padding(.top, 24)
.padding(.bottom, 80)
}
}
.background(
LinearGradient(
colors: selectedTheme.previewColors,
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
.animation(.easeInOut(duration: 0.4), value: selectedTheme)
)
.onAppear {
// Apply default theme on appear
selectedTheme.apply()
}
.accessibilityIdentifier(AccessibilityID.Onboarding.styleScreen)
}
}
// MARK: - Onboarding Theme Card
struct OnboardingThemeCard: View {
let theme: AppTheme
let isSelected: Bool
let action: () -> Void
var body: some View {
Button(action: action) {
VStack(spacing: 0) {
// Preview gradient with emoji
ZStack {
LinearGradient(
colors: theme.previewColors,
startPoint: .topLeading,
endPoint: .bottomTrailing
)
Text(theme.emoji)
.font(.system(size: 32))
.shadow(color: .black.opacity(0.2), radius: 2, x: 0, y: 1)
// Selected checkmark
if isSelected {
VStack {
HStack {
Spacer()
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 20))
.foregroundStyle(.white, .green)
.padding(6)
}
Spacer()
}
}
}
.frame(height: 70)
.clipShape(
UnevenRoundedRectangle(
topLeadingRadius: 14,
bottomLeadingRadius: 0,
bottomTrailingRadius: 0,
topTrailingRadius: 14
)
)
// Name and tagline
VStack(alignment: .leading, spacing: 2) {
Text(theme.name)
.font(.subheadline.weight(.semibold))
.foregroundColor(Color(UIColor.darkText))
.lineLimit(1)
Text(theme.tagline)
.font(.caption2)
.foregroundColor(Color(UIColor.darkGray))
.lineLimit(1)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 10)
.padding(.vertical, 8)
.background(Color.white)
.clipShape(
UnevenRoundedRectangle(
topLeadingRadius: 0,
bottomLeadingRadius: 14,
bottomTrailingRadius: 14,
topTrailingRadius: 0
)
)
}
.overlay(
RoundedRectangle(cornerRadius: 14)
.stroke(isSelected ? Color.white : Color.clear, lineWidth: 3)
)
.shadow(
color: .black.opacity(isSelected ? 0.25 : 0.15),
radius: isSelected ? 8 : 4,
x: 0,
y: isSelected ? 4 : 2
)
}
.buttonStyle(.plain)
}
}
struct OnboardingStyle_Previews: PreviewProvider {
static var previews: some View {
OnboardingStyle(onboardingData: OnboardingData())
}
}