From fd5100e6eda7c91efd4e642ad7e1b381da6b9c92 Mon Sep 17 00:00:00 2001 From: Trey t Date: Wed, 10 Dec 2025 09:09:33 -0600 Subject: [PATCH] Redesign onboarding with modern UI and subscription prompt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- Shared/Onboarding/views/OnboardingDay.swift | 193 ++++++++++----- Shared/Onboarding/views/OnboardingMain.swift | 49 ++-- Shared/Onboarding/views/OnboardingStyle.swift | 226 ++++++++++++++++++ .../views/OnboardingSubscription.swift | 194 +++++++++++++++ Shared/Onboarding/views/OnboardingTime.swift | 107 ++++++--- .../Onboarding/views/OnboardingWelcome.swift | 115 +++++++++ 6 files changed, 772 insertions(+), 112 deletions(-) create mode 100644 Shared/Onboarding/views/OnboardingStyle.swift create mode 100644 Shared/Onboarding/views/OnboardingSubscription.swift create mode 100644 Shared/Onboarding/views/OnboardingWelcome.swift diff --git a/Shared/Onboarding/views/OnboardingDay.swift b/Shared/Onboarding/views/OnboardingDay.swift index f9d8a83..03dd4d2 100644 --- a/Shared/Onboarding/views/OnboardingDay.swift +++ b/Shared/Onboarding/views/OnboardingDay.swift @@ -10,82 +10,167 @@ import SwiftUI enum DayOptions: Int, CaseIterable, RawRepresentable, Codable { case Today case Previous - + var localizedValue: String { switch self { - case .Today: - return String(localized: "onboarding_day_options_today") + return String(localized: "onboarding_day_options_today") case .Previous: - return String(localized: "onboarding_day_options_yesterday") + return String(localized: "onboarding_day_options_yesterday") } } } struct OnboardingDay: View { @ObservedObject var onboardingData: OnboardingData - - var previewText: String { - switch onboardingData.inputDay { - case .Today: - return String(localized: "onboarding_day_preview_text_today") - case .Previous: - return String(localized: "onboarding_day_preview_text_yesterday") - } - } - + var body: some View { ZStack { - GeometryReader { geometry in - VStack { - Spacer() - Image("bad", bundle: .main) - .foregroundColor(Color(UIColor.darkText)) - .opacity(0.04) - .scaleEffect(1.2, anchor: .trailing) - Spacer() - } - - ScrollView { - VStack(alignment: .leading) { - Text(String(localized: "onboarding_day_title")) - .font(.title) - .padding() - .fixedSize(horizontal: false, vertical: true) - .foregroundColor(.white) + // Gradient background + LinearGradient( + colors: [Color(hex: "4facfe"), Color(hex: "00f2fe")], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + .ignoresSafeArea() - - Picker(selection: $onboardingData.inputDay, - label: Text("")) { - ForEach(DayOptions.allCases, id: \.self) { day in - Text(day.localizedValue) - } - } - .labelsHidden() - .frame(minWidth: 0, maxWidth: .infinity) - .padding() - .pickerStyle(SegmentedPickerStyle()) - .colorScheme(.dark) - - Text(previewText) - .font(.title3) - .padding() - .fixedSize(horizontal: false, vertical: true) - .foregroundColor(.white) - } + VStack(spacing: 0) { + Spacer() + + // Icon + ZStack { + Circle() + .fill(.white.opacity(0.15)) + .frame(width: 120, height: 120) + + Image(systemName: "calendar") + .font(.system(size: 44)) + .foregroundColor(.white) } - .padding() + .padding(.bottom, 32) + + // Title + Text("Which day should\nyou rate?") + .font(.system(size: 28, weight: .bold, design: .rounded)) + .foregroundColor(.white) + .multilineTextAlignment(.center) + .padding(.bottom, 12) + + // Subtitle + Text("When you get your reminder, do you want to rate today or yesterday?") + .font(.system(size: 16, weight: .medium)) + .foregroundColor(.white.opacity(0.85)) + .multilineTextAlignment(.center) + .padding(.horizontal, 40) + + Spacer() + + // Options + VStack(spacing: 14) { + DayOptionCard( + title: "Today", + subtitle: "Rate the current day", + example: "e.g. Tue reminder → Rate Tue", + icon: "sun.max.fill", + isSelected: onboardingData.inputDay == .Today, + action: { onboardingData.inputDay = .Today } + ) + + DayOptionCard( + title: "Yesterday", + subtitle: "Rate the previous day", + example: "e.g. Tue reminder → Rate Mon", + icon: "moon.fill", + isSelected: onboardingData.inputDay == .Previous, + action: { onboardingData.inputDay = .Previous } + ) + } + .padding(.horizontal, 20) + + Spacer() + + // Tip + HStack(spacing: 12) { + Image(systemName: "lightbulb.fill") + .font(.system(size: 18)) + .foregroundColor(.yellow) + + Text("Tip: \"Yesterday\" works great for evening reminders") + .font(.system(size: 14, weight: .medium)) + .foregroundColor(.white.opacity(0.9)) + } + .padding(.horizontal, 30) + .padding(.bottom, 80) } } - .background(Color(hex: "ff9e0b")) + } +} + +struct DayOptionCard: View { + let title: String + let subtitle: String + let example: String + let icon: String + let isSelected: Bool + let action: () -> Void + + var body: some View { + Button(action: action) { + HStack(spacing: 14) { + // Icon + ZStack { + Circle() + .fill(isSelected ? Color.white : Color.white.opacity(0.2)) + .frame(width: 46, height: 46) + + Image(systemName: icon) + .font(.system(size: 20)) + .foregroundColor(isSelected ? Color(hex: "4facfe") : .white) + } + + // Text + VStack(alignment: .leading, spacing: 3) { + Text(title) + .font(.system(size: 17, weight: .semibold)) + .foregroundColor(isSelected ? Color(hex: "4facfe") : .white) + + Text(subtitle) + .font(.system(size: 13)) + .foregroundColor(isSelected ? Color(hex: "4facfe").opacity(0.8) : .white.opacity(0.8)) + + Text(example) + .font(.system(size: 11, weight: .medium)) + .foregroundColor(isSelected ? Color(hex: "4facfe").opacity(0.6) : .white.opacity(0.6)) + .lineLimit(1) + .minimumScaleFactor(0.8) + } + + Spacer(minLength: 8) + + // Checkmark + if isSelected { + Image(systemName: "checkmark.circle.fill") + .font(.system(size: 22)) + .foregroundColor(Color(hex: "4facfe")) + } + } + .padding(.horizontal, 16) + .padding(.vertical, 18) + .background( + RoundedRectangle(cornerRadius: 18) + .fill(isSelected ? .white : .white.opacity(0.15)) + .shadow(color: isSelected ? .black.opacity(0.1) : .clear, radius: 10, y: 5) + ) + } + .buttonStyle(.plain) } } struct OnboardingDay_Previews: PreviewProvider { static var previews: some View { - Group{ + Group { OnboardingDay(onboardingData: OnboardingData()) - + OnboardingDay(onboardingData: OnboardingData()) .preferredColorScheme(.dark) } diff --git a/Shared/Onboarding/views/OnboardingMain.swift b/Shared/Onboarding/views/OnboardingMain.swift index 05da6b2..c1c59f7 100644 --- a/Shared/Onboarding/views/OnboardingMain.swift +++ b/Shared/Onboarding/views/OnboardingMain.swift @@ -10,25 +10,31 @@ import SwiftUI struct OnboardingMain: View { @Environment(\.presentationMode) var presentationMode @State var onboardingData: OnboardingData - + @EnvironmentObject var iapManager: IAPManager + let updateBoardingDataClosure: ((OnboardingData) -> Void) - + var body: some View { TabView { - OnboardingTime(onboardingData: onboardingData) - - OnboardingDay(onboardingData: onboardingData) - - OnboardingCustomizeTwo(onboardingData: onboardingData) - - OnboardingCustomizeOne(onboardingData: onboardingData) + // 1. Welcome screen + OnboardingWelcome() -// OnboardingTitle(onboardingData: onboardingData) - - OnboardingWrapup(onboardingData: onboardingData, - completionClosure: { _ in - updateBoardingDataClosure(onboardingData) - }) + // 2. Reminder time + OnboardingTime(onboardingData: onboardingData) + + // 3. Which day to rate + OnboardingDay(onboardingData: onboardingData) + + // 4. Style customization + OnboardingStyle(onboardingData: onboardingData) + + // 5. Subscription benefits & completion + OnboardingSubscription( + onboardingData: onboardingData, + completionClosure: { data in + updateBoardingDataClosure(data) + } + ) } .ignoresSafeArea() .tabViewStyle(.page) @@ -37,18 +43,19 @@ struct OnboardingMain: View { } .interactiveDismissDisabled() } - + func setupAppearance() { UIPageControl.appearance().currentPageIndicatorTintColor = .white - UIPageControl.appearance().pageIndicatorTintColor = UIColor.black.withAlphaComponent(0.2) + UIPageControl.appearance().pageIndicatorTintColor = UIColor.white.withAlphaComponent(0.3) } } struct OnboardingMain_Previews: PreviewProvider { static var previews: some View { - OnboardingMain(onboardingData: OnboardingData(), - updateBoardingDataClosure: { _ in - - }) + OnboardingMain( + onboardingData: OnboardingData(), + updateBoardingDataClosure: { _ in } + ) + .environmentObject(IAPManager()) } } diff --git a/Shared/Onboarding/views/OnboardingStyle.swift b/Shared/Onboarding/views/OnboardingStyle.swift new file mode 100644 index 0000000..5bc8744 --- /dev/null +++ b/Shared/Onboarding/views/OnboardingStyle.swift @@ -0,0 +1,226 @@ +// +// 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()) + } +} diff --git a/Shared/Onboarding/views/OnboardingSubscription.swift b/Shared/Onboarding/views/OnboardingSubscription.swift new file mode 100644 index 0000000..326a1ca --- /dev/null +++ b/Shared/Onboarding/views/OnboardingSubscription.swift @@ -0,0 +1,194 @@ +// +// OnboardingSubscription.swift +// Feels +// +// Created by Claude Code on 12/10/24. +// + +import SwiftUI + +struct OnboardingSubscription: View { + @ObservedObject var onboardingData: OnboardingData + @EnvironmentObject var iapManager: IAPManager + @State private var showSubscriptionStore = false + + let completionClosure: ((OnboardingData) -> Void) + + var body: some View { + ZStack { + // Gradient background + LinearGradient( + colors: [Color(hex: "11998e"), Color(hex: "38ef7d")], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + .ignoresSafeArea() + + VStack(spacing: 0) { + Spacer() + + // Crown icon + ZStack { + Circle() + .fill(.white.opacity(0.15)) + .frame(width: 120, height: 120) + + Image(systemName: "crown.fill") + .font(.system(size: 48)) + .foregroundColor(.yellow) + } + .padding(.bottom, 24) + + // Title + Text("Unlock the Full\nExperience") + .font(.system(size: 28, weight: .bold, design: .rounded)) + .foregroundColor(.white) + .multilineTextAlignment(.center) + .padding(.bottom, 8) + + // Trial info + Text("Start your free 7-day trial") + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(.white.opacity(0.9)) + .padding(.bottom, 24) + + Spacer() + + // Benefits list + VStack(spacing: 0) { + BenefitRow( + icon: "calendar.badge.clock", + title: "Month & Year Views", + description: "See your mood patterns across months and years" + ) + + Divider() + .background(.white.opacity(0.2)) + + BenefitRow( + icon: "lightbulb.fill", + title: "Smart Insights", + description: "Discover trends and patterns in your moods" + ) + + Divider() + .background(.white.opacity(0.2)) + + BenefitRow( + icon: "clock.arrow.circlepath", + title: "Unlimited History", + description: "Access all your past mood data forever" + ) + + Divider() + .background(.white.opacity(0.2)) + + BenefitRow( + icon: "icloud.fill", + title: "Cloud Sync", + description: "Your data synced across all your devices" + ) + + Divider() + .background(.white.opacity(0.2)) + + BenefitRow( + icon: "heart.fill", + title: "Support Development", + description: "Help us keep improving Feels for everyone" + ) + } + .padding(.vertical, 8) + .background( + RoundedRectangle(cornerRadius: 20) + .fill(.white.opacity(0.15)) + ) + .padding(.horizontal, 24) + + Spacer() + + // Buttons + VStack(spacing: 12) { + // Subscribe button + Button(action: { + EventLogger.log(event: "onboarding_subscribe_tapped") + showSubscriptionStore = true + }) { + HStack { + Image(systemName: "sparkles") + .font(.system(size: 18, weight: .semibold)) + + Text("Start Free Trial") + .font(.system(size: 18, weight: .bold)) + } + .foregroundColor(Color(hex: "11998e")) + .frame(maxWidth: .infinity) + .padding(.vertical, 18) + .background( + RoundedRectangle(cornerRadius: 16) + .fill(.white) + .shadow(color: .black.opacity(0.15), radius: 10, y: 5) + ) + } + + // Skip button + Button(action: { + EventLogger.log(event: "onboarding_complete") + EventLogger.log(event: "onboarding_skip_subscription") + completionClosure(onboardingData) + }) { + Text("Maybe Later") + .font(.system(size: 16, weight: .medium)) + .foregroundColor(.white.opacity(0.8)) + } + .padding(.top, 4) + } + .padding(.horizontal, 24) + .padding(.bottom, 50) + } + } + .sheet(isPresented: $showSubscriptionStore, onDismiss: { + // After subscription store closes, complete onboarding + EventLogger.log(event: "onboarding_complete") + completionClosure(onboardingData) + }) { + FeelsSubscriptionStoreView() + } + } +} + +struct BenefitRow: View { + let icon: String + let title: String + let description: String + + var body: some View { + HStack(spacing: 16) { + Image(systemName: icon) + .font(.system(size: 22)) + .foregroundColor(.white) + .frame(width: 40) + + VStack(alignment: .leading, spacing: 2) { + Text(title) + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(.white) + + Text(description) + .font(.system(size: 13)) + .foregroundColor(.white.opacity(0.8)) + } + + Spacer() + } + .padding(.horizontal, 20) + .padding(.vertical, 14) + } +} + +struct OnboardingSubscription_Previews: PreviewProvider { + static var previews: some View { + OnboardingSubscription(onboardingData: OnboardingData(), completionClosure: { _ in }) + .environmentObject(IAPManager()) + } +} diff --git a/Shared/Onboarding/views/OnboardingTime.swift b/Shared/Onboarding/views/OnboardingTime.swift index 8efbc43..73157e5 100644 --- a/Shared/Onboarding/views/OnboardingTime.swift +++ b/Shared/Onboarding/views/OnboardingTime.swift @@ -9,53 +9,86 @@ import SwiftUI struct OnboardingTime: View { @ObservedObject var onboardingData: OnboardingData - + var formatter: DateFormatter { let dateFormatter = DateFormatter() dateFormatter.timeStyle = .short return dateFormatter } - + var body: some View { ZStack { - GeometryReader { geometry in - VStack(alignment: .leading) { - Spacer() - Image("horrible", bundle: .main) - .foregroundColor(Color(UIColor.darkText)) - .opacity(0.04) - .scaleEffect(1.2, anchor: .trailing) - Spacer() + // Gradient background + LinearGradient( + colors: [Color(hex: "f093fb"), Color(hex: "f5576c")], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + .ignoresSafeArea() + + VStack(spacing: 0) { + Spacer() + + // Icon + ZStack { + Circle() + .fill(.white.opacity(0.15)) + .frame(width: 120, height: 120) + + Image(systemName: "bell.fill") + .font(.system(size: 44)) + .foregroundColor(.white) } - - - ScrollView { - Text(String(localized: "onboarding_time_title")) - .font(.title) - .padding() - .fixedSize(horizontal: false, vertical: true) - .foregroundColor(Color(UIColor.white)) - - DatePicker("", selection: $onboardingData.date, - displayedComponents: .hourAndMinute) - .scaleEffect(2) - .labelsHidden() - .frame(minWidth: 0, maxWidth: .infinity) - .padding() - .padding([.top, .bottom], 25) - .colorScheme(.dark) - - Text(String(format: String(localized: "onboarding_time_body"), - formatter.string(from: onboardingData.date))) - .font(.title3) - .fixedSize(horizontal: false, vertical: true) - .padding() - .foregroundColor(Color(UIColor.white)) + .padding(.bottom, 32) + + // Title + Text("When should we\nremind you?") + .font(.system(size: 28, weight: .bold, design: .rounded)) + .foregroundColor(.white) + .multilineTextAlignment(.center) + .padding(.bottom, 12) + + // Subtitle + Text("Pick a time that works for your daily check-in") + .font(.system(size: 16, weight: .medium)) + .foregroundColor(.white.opacity(0.85)) + .multilineTextAlignment(.center) + .padding(.horizontal, 40) + + Spacer() + + // Time picker card + VStack(spacing: 16) { + DatePicker("", selection: $onboardingData.date, displayedComponents: .hourAndMinute) + .datePickerStyle(.wheel) + .labelsHidden() + .colorScheme(.light) } - .padding() + .padding(.vertical, 20) + .padding(.horizontal, 24) + .background( + RoundedRectangle(cornerRadius: 24) + .fill(.white) + .shadow(color: .black.opacity(0.1), radius: 20, y: 10) + ) + .padding(.horizontal, 30) + + Spacer() + + // Info text + HStack(spacing: 12) { + Image(systemName: "info.circle.fill") + .font(.system(size: 20)) + .foregroundColor(.white.opacity(0.8)) + + Text("You'll get a gentle reminder at \(formatter.string(from: onboardingData.date)) every day") + .font(.system(size: 14, weight: .medium)) + .foregroundColor(.white.opacity(0.9)) + } + .padding(.horizontal, 30) + .padding(.bottom, 80) } } - .background(Color(hex: "ff453a")) } } @@ -63,7 +96,7 @@ struct OnboardingTime_Previews: PreviewProvider { static var previews: some View { Group { OnboardingTime(onboardingData: OnboardingData()) - + OnboardingTime(onboardingData: OnboardingData()) .preferredColorScheme(.dark) } diff --git a/Shared/Onboarding/views/OnboardingWelcome.swift b/Shared/Onboarding/views/OnboardingWelcome.swift new file mode 100644 index 0000000..8f87360 --- /dev/null +++ b/Shared/Onboarding/views/OnboardingWelcome.swift @@ -0,0 +1,115 @@ +// +// OnboardingWelcome.swift +// Feels +// +// Created by Claude Code on 12/10/24. +// + +import SwiftUI + +struct OnboardingWelcome: View { + var body: some View { + ZStack { + // Gradient background + LinearGradient( + colors: [Color(hex: "667eea"), Color(hex: "764ba2")], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + .ignoresSafeArea() + + VStack(spacing: 0) { + Spacer() + + // App icon/logo area + ZStack { + Circle() + .fill(.white.opacity(0.15)) + .frame(width: 160, height: 160) + + Circle() + .fill(.white.opacity(0.2)) + .frame(width: 120, height: 120) + + Image(systemName: "heart.fill") + .font(.system(size: 50)) + .foregroundColor(.white) + } + .padding(.bottom, 40) + + // Title + Text("Welcome to Feels") + .font(.system(size: 34, weight: .bold, design: .rounded)) + .foregroundColor(.white) + .padding(.bottom, 12) + + // Subtitle + Text("Track your mood, discover patterns,\nand understand yourself better.") + .font(.system(size: 18, weight: .medium)) + .foregroundColor(.white.opacity(0.9)) + .multilineTextAlignment(.center) + .padding(.horizontal, 40) + + Spacer() + + // Feature highlights + VStack(spacing: 20) { + FeatureRow(icon: "bell.badge.fill", title: "Daily Reminders", description: "Never forget to log your mood") + FeatureRow(icon: "chart.bar.fill", title: "Beautiful Insights", description: "See your mood patterns over time") + FeatureRow(icon: "paintpalette.fill", title: "Fully Customizable", description: "Make it yours with themes & colors") + } + .padding(.horizontal, 30) + .padding(.bottom, 40) + + // Swipe hint + HStack(spacing: 8) { + Text("Swipe to get started") + .font(.subheadline.weight(.medium)) + .foregroundColor(.white.opacity(0.7)) + Image(systemName: "chevron.right") + .font(.subheadline.weight(.semibold)) + .foregroundColor(.white.opacity(0.7)) + } + .padding(.bottom, 60) + } + } + } +} + +struct FeatureRow: View { + let icon: String + let title: String + let description: String + + var body: some View { + HStack(spacing: 16) { + ZStack { + Circle() + .fill(.white.opacity(0.2)) + .frame(width: 50, height: 50) + + Image(systemName: icon) + .font(.system(size: 22)) + .foregroundColor(.white) + } + + VStack(alignment: .leading, spacing: 2) { + Text(title) + .font(.system(size: 16, weight: .semibold)) + .foregroundColor(.white) + + Text(description) + .font(.system(size: 14)) + .foregroundColor(.white.opacity(0.8)) + } + + Spacer() + } + } +} + +struct OnboardingWelcome_Previews: PreviewProvider { + static var previews: some View { + OnboardingWelcome() + } +}