Files
FamilyMealPlanner/MealMood/Services/ReviewPromptService.swift
T
alexandrev-tibco b45cb2a530 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>
2026-06-11 22:21:36 +02:00

48 lines
1.5 KiB
Swift

import UIKit
import StoreKit
@MainActor
final class ReviewPromptService {
static let shared = ReviewPromptService()
private let reviewCompletedKey = "review_funnel_completed"
private let lastPromptedCompletedWeeksKey = "review_funnel_last_prompted_completed_weeks"
private let minimumCompletedWeeksToStart = 1
private let promptIntervalWeeks = 2
private init() {}
func shouldShowFunnelAfterWeekCompletion(completedWeeks: Int) -> Bool {
guard !hasCompletedReviewFlow else { return false }
guard completedWeeks >= minimumCompletedWeeksToStart else { return false }
guard completedWeeks % promptIntervalWeeks == 0 else { return false }
let lastPrompted = UserDefaults.standard.integer(forKey: lastPromptedCompletedWeeksKey)
return completedWeeks > lastPrompted
}
func markFunnelShown(completedWeeks: Int) {
UserDefaults.standard.set(completedWeeks, forKey: lastPromptedCompletedWeeksKey)
}
func markReviewCompleted() {
UserDefaults.standard.set(true, forKey: reviewCompletedKey)
}
func requestFromSettings() {
requestReview()
}
var hasCompletedReviewFlow: Bool {
UserDefaults.standard.bool(forKey: reviewCompletedKey)
}
func requestReview() {
guard let scene = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.first(where: { $0.activationState == .foregroundActive }) else { return }
SKStoreReviewController.requestReview(in: scene)
}
}