From cc9f9f94274333c409480a5eec6725c61a1662c7 Mon Sep 17 00:00:00 2001 From: Trey t Date: Wed, 24 Dec 2025 10:54:01 -0600 Subject: [PATCH] Improve subscription UI and fix trial date handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lock Screen: - Add light/dark mode support with adaptive colors - Make passcode text tappable to trigger authentication Trial Date Fixes: - Fix IAPManager to read firstLaunchDate directly from UserDefaults - Add expiration date check (> Date()) before showing "expires in" text - Show "Trial expired" when trial end date is in the past - Disable subscription bypass in DEBUG mode for testing Month/Year Subscribe Prompts: - Redesign with gradient icons and compelling copy - Add fade mask (100% at top to 0% at 50%) for content behind - Position subscribe overlay on bottom half of screen - Month: purple/pink theme with calendar icon - Year: orange/pink theme with chart icon 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- Feels/Localizable.xcstrings | 39 +++++++ Shared/IAPManager.swift | 15 ++- Shared/Views/IAPWarningView.swift | 13 ++- Shared/Views/LockScreenView.swift | 106 ++++++++++++------ Shared/Views/MonthView/MonthView.swift | 88 ++++++++++++--- Shared/Views/PurchaseButtonView.swift | 6 +- .../Views/SettingsView/SettingsTabView.swift | 2 +- Shared/Views/YearView/YearView.swift | 84 ++++++++++---- 8 files changed, 275 insertions(+), 78 deletions(-) diff --git a/Feels/Localizable.xcstrings b/Feels/Localizable.xcstrings index 3d76be0..4ce8192 100644 --- a/Feels/Localizable.xcstrings +++ b/Feels/Localizable.xcstrings @@ -1183,6 +1183,10 @@ "comment" : "An accessibility label for the section of the settings view that indicates that Apple Health is not available on the user's device.", "isCommentAutoGenerated" : true }, + "are safe here" : { + "comment" : "A description of the safety of the app's \"Aurora background\".", + "isCommentAutoGenerated" : true + }, "Are you sure you want to delete this mood entry? This cannot be undone." : { "comment" : "An alert message displayed when the user attempts to delete a mood entry.", "isCommentAutoGenerated" : true, @@ -1269,6 +1273,7 @@ }, "Authenticate to access your mood data" : { "comment" : "A description of the purpose of the authentication process in the lock screen.", + "extractionState" : "stale", "isCommentAutoGenerated" : true, "localizations" : { "de" : { @@ -1309,6 +1314,10 @@ } } }, + "Authenticate to continue your journey" : { + "comment" : "A description of the action required to unlock the app.", + "isCommentAutoGenerated" : true + }, "Authentication Failed" : { "comment" : "An alert title when authentication fails.", "isCommentAutoGenerated" : true, @@ -4310,6 +4319,10 @@ } } }, + "Discover your emotional rhythm across the year. Spot trends and celebrate how far you've come." : { + "comment" : "A description of what the \"See Your Year at a Glance\" feature does.", + "isCommentAutoGenerated" : true + }, "Don't break your streak!" : { "comment" : "A description of the current streak or a motivational message.", "isCommentAutoGenerated" : true, @@ -4692,6 +4705,10 @@ } } }, + "Explore Your Mood History" : { + "comment" : "A title for a feature that allows users to explore their mood history.", + "isCommentAutoGenerated" : true + }, "Export" : { "comment" : "A button label that triggers data export.", "isCommentAutoGenerated" : true, @@ -5076,6 +5093,7 @@ }, "Feels is Locked" : { "comment" : "The title of the lock screen.", + "extractionState" : "stale", "isCommentAutoGenerated" : true, "localizations" : { "de" : { @@ -10781,6 +10799,13 @@ } } }, + "See your complete monthly journey. Track patterns and understand what shapes your days." : { + + }, + "See Your Year at a Glance" : { + "comment" : "A title for a feature that lets users see their year's emotional trends.", + "isCommentAutoGenerated" : true + }, "Select this mood" : { "comment" : "A hint that appears when a user taps on a mood button.", "isCommentAutoGenerated" : true @@ -11653,6 +11678,7 @@ }, "Subscribe to see your full year" : { "comment" : "A button label that appears when the user is subscribed to Feels.", + "extractionState" : "stale", "isCommentAutoGenerated" : true, "localizations" : { "de" : { @@ -13088,6 +13114,10 @@ } } }, + "Unlock Full History" : { + "comment" : "A button label that appears when a user is on a free trial and wants to unlock all of their mood history.", + "isCommentAutoGenerated" : true + }, "Unlock Premium" : { "comment" : "A button label that says \"Unlock Premium\".", "isCommentAutoGenerated" : true, @@ -13174,6 +13204,7 @@ }, "Unlock with %@" : { "comment" : "A button label that instructs the user to unlock their device using their biometric authentication method. The text inside the parentheses is replaced with the name of the biometric authentication method available on the user's device (e.g", + "extractionState" : "stale", "isCommentAutoGenerated" : true, "localizations" : { "de" : { @@ -13214,6 +13245,10 @@ } } }, + "Unlock Year Overview" : { + "comment" : "A button label that appears when the user is not a premium subscriber, encouraging them to subscribe to unlock more features.", + "isCommentAutoGenerated" : true + }, "Use Siri to Log Moods" : { "localizations" : { "de" : { @@ -14186,6 +14221,10 @@ } } } + }, + "Your Feelings" : { + "comment" : "The title of the main screen in the lock screen.", + "isCommentAutoGenerated" : true } }, "version" : "1.1" diff --git a/Shared/IAPManager.swift b/Shared/IAPManager.swift index cd69075..44f7631 100644 --- a/Shared/IAPManager.swift +++ b/Shared/IAPManager.swift @@ -33,8 +33,9 @@ class IAPManager: ObservableObject { // MARK: - Debug Toggle /// Set to `true` to bypass all subscription checks and grant full access (for development only) + /// Set to `false` to test trial/subscription behavior in DEBUG builds #if DEBUG - static let bypassSubscription = true + static let bypassSubscription = false #else static let bypassSubscription = false #endif @@ -59,8 +60,16 @@ class IAPManager: ObservableObject { // MARK: - Storage - @AppStorage(UserDefaultsStore.Keys.firstLaunchDate.rawValue, store: GroupUserDefaults.groupDefaults) - private var firstLaunchDate = Date() + /// Reads firstLaunchDate directly from UserDefaults to ensure we always get the latest value + /// (Using @AppStorage in a class doesn't auto-sync when other components update the same key) + private var firstLaunchDate: Date { + get { + GroupUserDefaults.groupDefaults.object(forKey: UserDefaultsStore.Keys.firstLaunchDate.rawValue) as? Date ?? Date() + } + set { + GroupUserDefaults.groupDefaults.set(newValue, forKey: UserDefaultsStore.Keys.firstLaunchDate.rawValue) + } + } // MARK: - Private diff --git a/Shared/Views/IAPWarningView.swift b/Shared/Views/IAPWarningView.swift index 7b8da69..96738b7 100644 --- a/Shared/Views/IAPWarningView.swift +++ b/Shared/Views/IAPWarningView.swift @@ -21,15 +21,20 @@ struct IAPWarningView: View { Image(systemName: "clock") .foregroundColor(.orange) - Text(String(localized: "iap_warning_view_title")) - .font(.body) - .foregroundColor(textColor) + if let expirationDate = iapManager.trialExpirationDate, expirationDate > Date() { + Text(String(localized: "iap_warning_view_title")) + .font(.body) + .foregroundColor(textColor) - if let expirationDate = iapManager.trialExpirationDate { Text(expirationDate, style: .relative) .font(.body) .bold() .foregroundColor(.orange) + } else { + Text(String(localized: "purchase_view_trial_expired")) + .font(.body) + .bold() + .foregroundColor(.orange) } } diff --git a/Shared/Views/LockScreenView.swift b/Shared/Views/LockScreenView.swift index eda0faa..746525c 100644 --- a/Shared/Views/LockScreenView.swift +++ b/Shared/Views/LockScreenView.swift @@ -23,6 +23,7 @@ struct MoodParticle: Identifiable { // MARK: - Aurora Gradient Background struct AuroraBackground: View { + @Environment(\.colorScheme) private var colorScheme @State private var animateGradient = false private let moodColors: [Color] = [ @@ -33,14 +34,20 @@ struct AuroraBackground: View { Color(hex: "ff453a"), // horrible - red ] + private var isDark: Bool { colorScheme == .dark } + var body: some View { ZStack { - // Base dark gradient + // Base gradient - adapts to color scheme LinearGradient( - colors: [ + colors: isDark ? [ Color(hex: "0a0a0f"), Color(hex: "12121a"), Color(hex: "0d0d14") + ] : [ + Color(hex: "f8f9fa"), + Color(hex: "e9ecef"), + Color(hex: "f1f3f5") ], startPoint: .topLeading, endPoint: .bottomTrailing @@ -49,8 +56,8 @@ struct AuroraBackground: View { // Aurora layer 1 - green/blue EllipticalGradient( colors: [ - moodColors[0].opacity(0.3), - moodColors[2].opacity(0.15), + moodColors[0].opacity(isDark ? 0.3 : 0.2), + moodColors[2].opacity(isDark ? 0.15 : 0.1), .clear ], center: .topLeading, @@ -63,8 +70,8 @@ struct AuroraBackground: View { // Aurora layer 2 - yellow/orange EllipticalGradient( colors: [ - moodColors[1].opacity(0.2), - moodColors[3].opacity(0.1), + moodColors[1].opacity(isDark ? 0.2 : 0.15), + moodColors[3].opacity(isDark ? 0.1 : 0.08), .clear ], center: .bottomTrailing, @@ -77,7 +84,7 @@ struct AuroraBackground: View { // Aurora layer 3 - subtle red accent RadialGradient( colors: [ - moodColors[4].opacity(0.15), + moodColors[4].opacity(isDark ? 0.15 : 0.1), .clear ], center: UnitPoint(x: 0.8, y: 0.3), @@ -89,7 +96,7 @@ struct AuroraBackground: View { // Noise texture overlay Rectangle() - .fill(.white.opacity(0.015)) + .fill((isDark ? Color.white : Color.black).opacity(0.015)) .background( Canvas { context, size in for _ in 0..<1000 { @@ -98,7 +105,7 @@ struct AuroraBackground: View { let opacity = Double.random(in: 0.01...0.04) context.fill( Path(ellipseIn: CGRect(x: x, y: y, width: 1, height: 1)), - with: .color(.white.opacity(opacity)) + with: .color((isDark ? Color.white : Color.black).opacity(opacity)) ) } } @@ -190,6 +197,7 @@ struct FloatingAnimation: ViewModifier { // MARK: - Central Breathing Orb struct BreathingOrb: View { + @Environment(\.colorScheme) private var colorScheme @State private var breathe = false @State private var rotate = false @@ -201,6 +209,8 @@ struct BreathingOrb: View { Color(hex: "ff453a"), ] + private var isDark: Bool { colorScheme == .dark } + var body: some View { ZStack { // Outer glow @@ -215,7 +225,7 @@ struct BreathingOrb: View { ) .frame(width: 180, height: 180) .blur(radius: 40) - .opacity(0.6) + .opacity(isDark ? 0.6 : 0.5) .scaleEffect(breathe ? 1.2 : 0.9) .rotationEffect(.degrees(rotate ? 360 : 0)) @@ -229,7 +239,7 @@ struct BreathingOrb: View { ) .frame(width: 120, height: 120) .blur(radius: 20) - .opacity(0.8) + .opacity(isDark ? 0.8 : 0.6) .scaleEffect(breathe ? 1.1 : 0.95) .rotationEffect(.degrees(rotate ? -360 : 0)) @@ -237,10 +247,14 @@ struct BreathingOrb: View { Circle() .fill( RadialGradient( - colors: [ + colors: isDark ? [ .white.opacity(0.9), .white.opacity(0.3), .clear + ] : [ + .white, + .white.opacity(0.6), + .clear ], center: .center, startRadius: 0, @@ -249,12 +263,13 @@ struct BreathingOrb: View { ) .frame(width: 80, height: 80) .scaleEffect(breathe ? 1.05 : 0.98) + .shadow(color: .black.opacity(isDark ? 0 : 0.1), radius: 10) // Glossy highlight Ellipse() .fill( LinearGradient( - colors: [.white.opacity(0.4), .clear], + colors: [.white.opacity(isDark ? 0.4 : 0.8), .clear], startPoint: .top, endPoint: .center ) @@ -283,6 +298,7 @@ struct BreathingOrb: View { // MARK: - Glassmorphic Button struct GlassButton: View { + @Environment(\.colorScheme) private var colorScheme let icon: String let title: String let action: () -> Void @@ -290,6 +306,10 @@ struct GlassButton: View { @State private var isPressed = false @State private var pulse = false + private var isDark: Bool { colorScheme == .dark } + private var foregroundColor: Color { isDark ? .white : .primary } + private var accentOpacity: Double { isDark ? 0.15 : 0.1 } + var body: some View { Button(action: action) { HStack(spacing: 14) { @@ -297,24 +317,24 @@ struct GlassButton: View { // Pulse ring Circle() .stroke(lineWidth: 2) - .foregroundColor(.white.opacity(0.3)) + .foregroundColor(foregroundColor.opacity(0.3)) .frame(width: 44, height: 44) .scaleEffect(pulse ? 1.3 : 1) .opacity(pulse ? 0 : 0.6) // Icon background Circle() - .fill(.white.opacity(0.15)) + .fill(foregroundColor.opacity(accentOpacity)) .frame(width: 44, height: 44) Image(systemName: icon) .font(.system(size: 20, weight: .medium)) - .foregroundColor(.white) + .foregroundColor(foregroundColor) } Text(title) .font(.system(size: 17, weight: .semibold, design: .rounded)) - .foregroundColor(.white) + .foregroundColor(foregroundColor) } .frame(maxWidth: .infinity) .padding(.vertical, 18) @@ -324,16 +344,15 @@ struct GlassButton: View { // Glass effect RoundedRectangle(cornerRadius: 20) .fill(.ultraThinMaterial) - .opacity(0.8) // Border gradient RoundedRectangle(cornerRadius: 20) .stroke( LinearGradient( colors: [ - .white.opacity(0.4), - .white.opacity(0.1), - .white.opacity(0.2) + foregroundColor.opacity(0.3), + foregroundColor.opacity(0.1), + foregroundColor.opacity(0.15) ], startPoint: .topLeading, endPoint: .bottomTrailing @@ -341,11 +360,11 @@ struct GlassButton: View { lineWidth: 1 ) - // Inner shadow + // Inner highlight RoundedRectangle(cornerRadius: 20) .fill( LinearGradient( - colors: [.white.opacity(0.1), .clear], + colors: [foregroundColor.opacity(0.08), .clear], startPoint: .top, endPoint: .bottom ) @@ -382,11 +401,16 @@ struct GlassButton: View { // MARK: - Main Lock Screen View struct LockScreenView: View { - + @Environment(\.colorScheme) private var colorScheme @ObservedObject var authManager: BiometricAuthManager @State private var showError = false @State private var showContent = false + private var isDark: Bool { colorScheme == .dark } + private var primaryText: Color { isDark ? .white : .primary } + private var secondaryText: Color { isDark ? .white.opacity(0.7) : .secondary } + private var tertiaryText: Color { isDark ? .white.opacity(0.5) : .secondary.opacity(0.8) } + var body: some View { ZStack { // Aurora background @@ -411,11 +435,11 @@ struct LockScreenView: View { VStack(spacing: 12) { Text("Your Feelings") .font(.system(size: 32, weight: .light, design: .serif)) - .foregroundColor(.white) + .foregroundColor(primaryText) Text("are safe here") .font(.system(size: 32, weight: .ultraLight, design: .serif)) - .foregroundColor(.white.opacity(0.7)) + .foregroundColor(secondaryText) } .opacity(showContent ? 1 : 0) .offset(y: showContent ? 0 : 20) @@ -425,7 +449,7 @@ struct LockScreenView: View { Text("Authenticate to continue your journey") .font(.system(size: 14, weight: .regular, design: .rounded)) - .foregroundColor(.white.opacity(0.5)) + .foregroundColor(tertiaryText) .opacity(showContent ? 1 : 0) Spacer() @@ -447,13 +471,23 @@ struct LockScreenView: View { .offset(y: showContent ? 0 : 30) .padding(.horizontal, 32) - // Passcode hint + // Passcode button - tappable to authenticate with passcode if authManager.canUseDevicePasscode { - Text("Or use your device passcode") - .font(.system(size: 12, weight: .regular, design: .rounded)) - .foregroundColor(.white.opacity(0.35)) - .padding(.top, 16) - .opacity(showContent ? 1 : 0) + Button { + Task { + let success = await authManager.authenticate() + if !success { + showError = true + } + } + } label: { + Text("Or use your device passcode") + .font(.system(size: 13, weight: .medium, design: .rounded)) + .foregroundColor(isDark ? .white.opacity(0.5) : .accentColor) + } + .disabled(authManager.isAuthenticating) + .padding(.top, 16) + .opacity(showContent ? 1 : 0) } Spacer() @@ -490,12 +524,12 @@ struct LockScreenView: View { // MARK: - Preview -#Preview("Lock Screen - Face ID") { +#Preview("Lock Screen - Dark") { LockScreenView(authManager: BiometricAuthManager()) .preferredColorScheme(.dark) } -#Preview("Lock Screen - Touch ID") { +#Preview("Lock Screen - Light") { LockScreenView(authManager: BiometricAuthManager()) - .preferredColorScheme(.dark) + .preferredColorScheme(.light) } diff --git a/Shared/Views/MonthView/MonthView.swift b/Shared/Views/MonthView/MonthView.swift index ad36c4d..bfbd501 100644 --- a/Shared/Views/MonthView/MonthView.swift +++ b/Shared/Views/MonthView/MonthView.swift @@ -109,6 +109,21 @@ struct MonthView: View { ) } .scrollDisabled(iapManager.shouldShowPaywall) + .mask( + // Fade effect when paywall should show: 100% at top, 0% halfway down + iapManager.shouldShowPaywall ? + AnyView( + LinearGradient( + gradient: Gradient(stops: [ + .init(color: .black, location: 0), + .init(color: .black, location: 0.3), + .init(color: .clear, location: 0.5) + ]), + startPoint: .top, + endPoint: .bottom + ) + ) : AnyView(Color.black) + ) } // Hidden text to trigger updates when custom tint changes @@ -116,27 +131,72 @@ struct MonthView: View { .hidden() if iapManager.shouldShowPaywall { - // Paywall overlay - tap to show subscription store - Color.black.opacity(0.3) - .ignoresSafeArea() - .onTapGesture { - showSubscriptionStore = true + // Premium month history prompt - bottom half + VStack(spacing: 20) { + // Icon + ZStack { + Circle() + .fill( + LinearGradient( + colors: [.purple.opacity(0.2), .pink.opacity(0.2)], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + .frame(width: 80, height: 80) + + Image(systemName: "calendar.badge.clock") + .font(.title) + .foregroundStyle( + LinearGradient( + colors: [.purple, .pink], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) } - VStack { - Spacer() + // Text + VStack(spacing: 10) { + Text("Explore Your Mood History") + .font(.title3.weight(.bold)) + .foregroundColor(textColor) + .multilineTextAlignment(.center) + + Text("See your complete monthly journey. Track patterns and understand what shapes your days.") + .font(.subheadline) + .foregroundColor(textColor.opacity(0.7)) + .multilineTextAlignment(.center) + .padding(.horizontal, 24) + } + + // Subscribe button Button { showSubscriptionStore = true } label: { - Text(String(localized: "subscription_required_button")) - .font(.headline) - .foregroundColor(.white) - .frame(maxWidth: .infinity) - .padding() - .background(RoundedRectangle(cornerRadius: 10).fill(Color.pink)) + HStack { + Image(systemName: "calendar") + Text("Unlock Full History") + } + .font(.headline.weight(.bold)) + .foregroundColor(.white) + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .background( + LinearGradient( + colors: [.purple, .pink], + startPoint: .leading, + endPoint: .trailing + ) + ) + .clipShape(RoundedRectangle(cornerRadius: 14)) } - .padding() + .padding(.horizontal, 24) } + .padding(.vertical, 24) + .frame(maxWidth: .infinity) + .background(theme.currentTheme.bg) + .frame(maxHeight: .infinity, alignment: .bottom) } else if iapManager.shouldShowTrialWarning { VStack { Spacer() diff --git a/Shared/Views/PurchaseButtonView.swift b/Shared/Views/PurchaseButtonView.swift index 84eba00..6912ee0 100644 --- a/Shared/Views/PurchaseButtonView.swift +++ b/Shared/Views/PurchaseButtonView.swift @@ -180,8 +180,12 @@ struct PurchaseButtonView: View { Image(systemName: "clock") .foregroundColor(.orange) - if let expirationDate = iapManager.trialExpirationDate { + if let expirationDate = iapManager.trialExpirationDate, expirationDate > Date() { Text("\(Text(String(localized: "purchase_view_trial_expires_in")).foregroundColor(textColor)) \(Text(expirationDate, style: .relative).foregroundColor(.orange).bold())") + } else { + Text(String(localized: "purchase_view_trial_expired")) + .foregroundColor(.orange) + .bold() } } .font(.body) diff --git a/Shared/Views/SettingsView/SettingsTabView.swift b/Shared/Views/SettingsView/SettingsTabView.swift index 0bc682b..6ea80e4 100644 --- a/Shared/Views/SettingsView/SettingsTabView.swift +++ b/Shared/Views/SettingsView/SettingsTabView.swift @@ -95,7 +95,7 @@ struct UpgradeBannerView: View { .font(.subheadline.weight(.medium)) .foregroundColor(.orange) - if let expirationDate = trialExpirationDate { + if let expirationDate = trialExpirationDate, expirationDate > Date() { Text("\(Text("Trial expires in ").font(.subheadline.weight(.medium)).foregroundColor(textColor.opacity(0.8)))\(Text(expirationDate, style: .relative).font(.subheadline.weight(.bold)).foregroundColor(.orange))") } else { Text("Trial expired") diff --git a/Shared/Views/YearView/YearView.swift b/Shared/Views/YearView/YearView.swift index 7266d65..cef2ca6 100644 --- a/Shared/Views/YearView/YearView.swift +++ b/Shared/Views/YearView/YearView.swift @@ -66,14 +66,14 @@ struct YearView: View { } .scrollDisabled(iapManager.shouldShowPaywall) .mask( - // Fade effect when paywall should show: 100% at top, 0% at bottom + // Fade effect when paywall should show: 100% at top, 0% halfway down iapManager.shouldShowPaywall ? AnyView( LinearGradient( gradient: Gradient(stops: [ .init(color: .black, location: 0), .init(color: .black, location: 0.3), - .init(color: .clear, location: 1.0) + .init(color: .clear, location: 0.5) ]), startPoint: .top, endPoint: .bottom @@ -83,26 +83,72 @@ struct YearView: View { } if iapManager.shouldShowPaywall { - VStack { - Spacer() - VStack(spacing: 16) { - Text("Subscribe to see your full year") - .font(.headline) - .foregroundColor(textColor) + // Premium year overview prompt - bottom half + VStack(spacing: 20) { + // Icon + ZStack { + Circle() + .fill( + LinearGradient( + colors: [.orange.opacity(0.2), .pink.opacity(0.2)], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + .frame(width: 80, height: 80) - Button { - showSubscriptionStore = true - } label: { - Text(String(localized: "subscription_required_button")) - .font(.headline) - .foregroundColor(.white) - .frame(maxWidth: .infinity) - .padding() - .background(RoundedRectangle(cornerRadius: 10).fill(Color.pink)) - } + Image(systemName: "chart.bar.xaxis") + .font(.title) + .foregroundStyle( + LinearGradient( + colors: [.orange, .pink], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) } - .padding() + + // Text + VStack(spacing: 10) { + Text("See Your Year at a Glance") + .font(.title3.weight(.bold)) + .foregroundColor(textColor) + .multilineTextAlignment(.center) + + Text("Discover your emotional rhythm across the year. Spot trends and celebrate how far you've come.") + .font(.subheadline) + .foregroundColor(textColor.opacity(0.7)) + .multilineTextAlignment(.center) + .padding(.horizontal, 24) + } + + // Subscribe button + Button { + showSubscriptionStore = true + } label: { + HStack { + Image(systemName: "chart.bar.fill") + Text("Unlock Year Overview") + } + .font(.headline.weight(.bold)) + .foregroundColor(.white) + .frame(maxWidth: .infinity) + .padding(.vertical, 14) + .background( + LinearGradient( + colors: [.orange, .pink], + startPoint: .leading, + endPoint: .trailing + ) + ) + .clipShape(RoundedRectangle(cornerRadius: 14)) + } + .padding(.horizontal, 24) } + .padding(.vertical, 24) + .frame(maxWidth: .infinity) + .background(theme.currentTheme.bg) + .frame(maxHeight: .infinity, alignment: .bottom) } else if iapManager.shouldShowTrialWarning { VStack { Spacer()