// // 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()) } }