1.1.5: conversion funnel overhaul
Paywall: - Redesigned PremiumView with yearly/monthly/lifetime side by side - 7-day free trial badge with savings % vs monthly - Social proof line, BEST VALUE highlight on yearly - StoreManager: new yearlyProduct, lifetimeProduct, yearlySavingsPercent, hasTrialAvailable Funnel: - Paywall step added to onboarding (PaywallStepView) - Contextual modal at dish limit (replaces silent block) - Visible "X/15 dishes" counter in DishListView - BottomPromoBanner rotates AdMob with internal "Remove ads" CTA (1 in 4) - Free dish limit lowered 20 → 15 - Review prompt earlier: 2 weeks → 1 week Analytics: - paywall_viewed/dismissed with source + seconds_on_screen - dish_limit_hit, week_limit_hit, free_trial_started events - Every PremiumView call site now passes its source StoreKit: - MealMood.storekit: added yearly $19.99 + 7d trial, monthly trial, lifetime $39.99 - AppStoreConnect-1.1.5-setup.md with full manual setup instructions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ struct HomeView: View {
|
||||
@StateObject private var viewModel = HomeViewModel()
|
||||
@State private var editingDish: Dish?
|
||||
@State private var showPremiumFromExport: Bool = false
|
||||
@State private var paywallSource: String = "unknown"
|
||||
@State private var showMonthlyHistory: Bool = false
|
||||
@State private var showStats: Bool = false
|
||||
@State private var showWeekLimitUpsell: Bool = false
|
||||
@@ -111,6 +112,8 @@ struct HomeView: View {
|
||||
withAnimation(.easeInOut(duration: 0.2)) {
|
||||
showWeekLimitUpsell = true
|
||||
}
|
||||
AnalyticsService.logWeekLimitHit()
|
||||
paywallSource = "week_limit"
|
||||
showPremiumFromExport = true
|
||||
HapticManager.shared.notification(type: .warning)
|
||||
}
|
||||
@@ -218,6 +221,7 @@ struct HomeView: View {
|
||||
messageKey: "premium_limit_future_weeks",
|
||||
actionTitleKey: "premium_subscribe"
|
||||
) {
|
||||
paywallSource = "week_limit_banner"
|
||||
showPremiumFromExport = true
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
@@ -267,7 +271,7 @@ struct HomeView: View {
|
||||
.frame(maxHeight: .infinity, alignment: .top)
|
||||
.safeAreaInset(edge: .bottom, spacing: 0) {
|
||||
if !settings.isPremium && !isRunningOnMac {
|
||||
AdBannerView()
|
||||
BottomPromoBanner(settings: settings)
|
||||
}
|
||||
}
|
||||
.alert("reset_title", isPresented: $viewModel.showResetAlert) {
|
||||
@@ -312,6 +316,7 @@ struct HomeView: View {
|
||||
.alert("onboarding_premium_prompt_title", isPresented: $showPostOnboardingPremiumPrompt) {
|
||||
Button("onboarding_premium_prompt_cta") {
|
||||
UserDefaults.standard.set(false, forKey: OnboardingViewModel.pendingPremiumPromptKey)
|
||||
paywallSource = "post_onboarding"
|
||||
showPremiumFromExport = true
|
||||
}
|
||||
Button("reset_cancel", role: .cancel) {
|
||||
@@ -459,7 +464,7 @@ struct HomeView: View {
|
||||
}
|
||||
.sheet(isPresented: $showPremiumFromExport) {
|
||||
NavigationStack {
|
||||
PremiumView(settings: settings)
|
||||
PremiumView(settings: settings, source: paywallSource)
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showExportStylePicker) {
|
||||
@@ -727,6 +732,8 @@ struct HomeView: View {
|
||||
withAnimation(.easeInOut(duration: 0.2)) {
|
||||
showWeekLimitUpsell = true
|
||||
}
|
||||
AnalyticsService.logWeekLimitHit()
|
||||
paywallSource = "week_picker"
|
||||
showPremiumFromExport = true
|
||||
HapticManager.shared.notification(type: .warning)
|
||||
}
|
||||
@@ -819,6 +826,7 @@ struct HomeView: View {
|
||||
.buttonStyle(.plain)
|
||||
} else {
|
||||
Button {
|
||||
paywallSource = "share_week"
|
||||
showPremiumFromExport = true
|
||||
} label: {
|
||||
Label("share_week_button", systemImage: "star.fill")
|
||||
|
||||
Reference in New Issue
Block a user