Files
Reflect/Shared/Onboarding/views/OnboardingStyle.swift
Trey t fd5100e6ed Redesign onboarding with modern UI and subscription prompt
Complete overhaul of onboarding flow with 5 screens:
1. Welcome - Gradient intro with feature highlights
2. Time - Clean card-based time picker for reminders
3. Day - Tappable cards for today/yesterday selection
4. Style - Horizontal scrollable icon & color pickers
5. Subscription - Benefits list with free trial CTA

Design improvements:
- Beautiful gradient backgrounds on each screen
- Consistent typography and visual hierarchy
- White page indicators visible on all backgrounds
- Haptic feedback on selections
- "Maybe Later" option on subscription screen

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-10 09:09:33 -06:00

227 lines
7.8 KiB
Swift

//
// OnboardingStyle.swift
// Feels
//
// Created by Claude Code on 12/10/24.
//
import SwiftUI
struct OnboardingStyle: View {
@ObservedObject var onboardingData: OnboardingData
@AppStorage(UserDefaultsStore.Keys.moodTint.rawValue, store: GroupUserDefaults.groupDefaults) private var moodTint: MoodTints = .Default
@AppStorage(UserDefaultsStore.Keys.moodImages.rawValue, store: GroupUserDefaults.groupDefaults) private var imagePack: MoodImages = .FontAwesome
var body: some View {
ZStack {
// Gradient background
LinearGradient(
colors: [Color(hex: "fa709a"), Color(hex: "fee140")],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
VStack(spacing: 0) {
Spacer()
// Icon
ZStack {
Circle()
.fill(.white.opacity(0.15))
.frame(width: 120, height: 120)
Image(systemName: "paintpalette.fill")
.font(.system(size: 44))
.foregroundColor(.white)
}
.padding(.bottom, 32)
// Title
Text("Make it yours")
.font(.system(size: 28, weight: .bold, design: .rounded))
.foregroundColor(.white)
.padding(.bottom, 12)
// Subtitle
Text("Choose your favorite style")
.font(.system(size: 16, weight: .medium))
.foregroundColor(.white.opacity(0.85))
Spacer()
// Preview card
OnboardingStylePreview(moodTint: moodTint, imagePack: imagePack)
.padding(.horizontal, 24)
.padding(.bottom, 24)
// Icon Style Section
VStack(alignment: .leading, spacing: 12) {
Text("Icon Style")
.font(.system(size: 14, weight: .semibold))
.foregroundColor(.white.opacity(0.9))
.padding(.horizontal, 24)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12) {
ForEach(MoodImages.allCases, id: \.rawValue) { pack in
OnboardingIconPackOption(
pack: pack,
moodTint: moodTint,
isSelected: imagePack == pack,
action: {
let impactMed = UIImpactFeedbackGenerator(style: .medium)
impactMed.impactOccurred()
imagePack = pack
}
)
}
}
.padding(.horizontal, 24)
}
}
.padding(.bottom, 20)
// Color Theme Section
VStack(alignment: .leading, spacing: 12) {
Text("Mood Colors")
.font(.system(size: 14, weight: .semibold))
.foregroundColor(.white.opacity(0.9))
.padding(.horizontal, 24)
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 12) {
ForEach(MoodTints.defaultOptions, id: \.rawValue) { tint in
OnboardingTintOption(
tint: tint,
isSelected: moodTint == tint,
action: {
let impactMed = UIImpactFeedbackGenerator(style: .medium)
impactMed.impactOccurred()
moodTint = tint
}
)
}
}
.padding(.horizontal, 24)
}
}
Spacer()
// Hint
HStack(spacing: 8) {
Image(systemName: "arrow.left.arrow.right")
.font(.system(size: 14))
Text("You can change these anytime in Customize")
.font(.system(size: 13, weight: .medium))
}
.foregroundColor(.white.opacity(0.7))
.padding(.bottom, 80)
}
}
}
}
// MARK: - Preview Card
struct OnboardingStylePreview: View {
let moodTint: MoodTints
let imagePack: MoodImages
var body: some View {
HStack(spacing: 16) {
imagePack.icon(forMood: .good)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 44, height: 44)
.foregroundColor(moodTint.color(forMood: .good))
VStack(alignment: .leading, spacing: 4) {
Text("Wednesday - 10th")
.font(.system(size: 17, weight: .semibold))
.foregroundColor(.white)
Text(Mood.good.strValue)
.font(.system(size: 14))
.foregroundColor(.white.opacity(0.8))
}
Spacer()
}
.padding(20)
.background(
RoundedRectangle(cornerRadius: 16)
.fill(.white.opacity(0.2))
)
}
}
// MARK: - Icon Pack Option
struct OnboardingIconPackOption: View {
let pack: MoodImages
let moodTint: MoodTints
let isSelected: Bool
let action: () -> Void
var body: some View {
Button(action: action) {
HStack(spacing: 6) {
ForEach([Mood.great, .good, .average], id: \.self) { mood in
pack.icon(forMood: mood)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 24, height: 24)
.foregroundColor(moodTint.color(forMood: mood))
}
}
.padding(.horizontal, 16)
.padding(.vertical, 14)
.background(
RoundedRectangle(cornerRadius: 14)
.fill(isSelected ? .white : .white.opacity(0.2))
)
.overlay(
RoundedRectangle(cornerRadius: 14)
.stroke(isSelected ? Color.clear : .white.opacity(0.3), lineWidth: 1)
)
}
.buttonStyle(.plain)
}
}
// MARK: - Tint Option
struct OnboardingTintOption: View {
let tint: MoodTints
let isSelected: Bool
let action: () -> Void
var body: some View {
Button(action: action) {
HStack(spacing: 4) {
ForEach([Mood.great, .good, .average, .bad, .horrible], id: \.self) { mood in
Circle()
.fill(tint.color(forMood: mood))
.frame(width: 20, height: 20)
}
}
.padding(.horizontal, 14)
.padding(.vertical, 12)
.background(
RoundedRectangle(cornerRadius: 14)
.fill(isSelected ? .white : .white.opacity(0.2))
)
.overlay(
RoundedRectangle(cornerRadius: 14)
.stroke(isSelected ? Color.clear : .white.opacity(0.3), lineWidth: 1)
)
}
.buttonStyle(.plain)
}
}
struct OnboardingStyle_Previews: PreviewProvider {
static var previews: some View {
OnboardingStyle(onboardingData: OnboardingData())
}
}