b45cb2a530
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>
153 lines
4.5 KiB
Swift
153 lines
4.5 KiB
Swift
import FirebaseAnalytics
|
|
|
|
enum AnalyticsEvent {
|
|
// Onboarding
|
|
static let onboardingCompleted = "onboarding_completed"
|
|
|
|
// Dishes
|
|
static let dishAdded = "dish_added"
|
|
static let dishDeleted = "dish_deleted"
|
|
|
|
// Planner
|
|
static let mealAssigned = "meal_assigned"
|
|
static let mealCleared = "meal_cleared"
|
|
static let weekReset = "week_reset"
|
|
static let weekPlanShared = "week_plan_shared"
|
|
|
|
// Premium
|
|
static let premiumUpgradeTapped = "premium_upgrade_tapped"
|
|
static let premiumPurchased = "premium_purchased"
|
|
static let premiumRestored = "premium_restored"
|
|
|
|
// Planner
|
|
static let autoAssignUsed = "auto_assign_used"
|
|
|
|
// Onboarding
|
|
static let onboardingStepViewed = "onboarding_step_viewed"
|
|
|
|
// Settings
|
|
static let iCloudSyncToggled = "icloud_sync_toggled"
|
|
static let notificationsToggled = "notifications_toggled"
|
|
static let languageChanged = "language_changed"
|
|
|
|
// Stats
|
|
static let statsViewed = "stats_viewed"
|
|
|
|
// Feedback loop
|
|
static let weekRated = "week_rated"
|
|
|
|
// Conversion funnel (1.1.5)
|
|
static let paywallViewed = "paywall_viewed"
|
|
static let paywallDismissed = "paywall_dismissed"
|
|
static let dishLimitHit = "dish_limit_hit"
|
|
static let weekLimitHit = "week_limit_hit"
|
|
static let freeTrialStarted = "free_trial_started"
|
|
}
|
|
|
|
enum AnalyticsService {
|
|
static func logEvent(_ name: String, parameters: [String: Any]? = nil) {
|
|
Analytics.logEvent(name, parameters: parameters)
|
|
}
|
|
|
|
static func logScreenView(_ screenName: String) {
|
|
Analytics.logEvent(AnalyticsEventScreenView, parameters: [
|
|
AnalyticsParameterScreenName: screenName,
|
|
AnalyticsParameterScreenClass: screenName
|
|
])
|
|
}
|
|
|
|
// MARK: - Convenience methods
|
|
|
|
static func logDishAdded(tagCount: Int) {
|
|
logEvent(AnalyticsEvent.dishAdded, parameters: ["tag_count": tagCount])
|
|
}
|
|
|
|
static func logDishDeleted() {
|
|
logEvent(AnalyticsEvent.dishDeleted)
|
|
}
|
|
|
|
static func logMealAssigned() {
|
|
logEvent(AnalyticsEvent.mealAssigned)
|
|
}
|
|
|
|
static func logMealCleared() {
|
|
logEvent(AnalyticsEvent.mealCleared)
|
|
}
|
|
|
|
static func logWeekReset() {
|
|
logEvent(AnalyticsEvent.weekReset)
|
|
}
|
|
|
|
static func logWeekPlanShared(format: String) {
|
|
logEvent(AnalyticsEvent.weekPlanShared, parameters: ["format": format])
|
|
}
|
|
|
|
static func logPremiumUpgradeTapped(source: String) {
|
|
logEvent(AnalyticsEvent.premiumUpgradeTapped, parameters: ["source": source])
|
|
}
|
|
|
|
static func logPremiumPurchased() {
|
|
logEvent(AnalyticsEvent.premiumPurchased)
|
|
}
|
|
|
|
static func logPremiumRestored() {
|
|
logEvent(AnalyticsEvent.premiumRestored)
|
|
}
|
|
|
|
static func logOnboardingCompleted() {
|
|
logEvent(AnalyticsEvent.onboardingCompleted)
|
|
}
|
|
|
|
static func logOnboardingStepViewed(step: Int) {
|
|
logEvent(AnalyticsEvent.onboardingStepViewed, parameters: ["step": step])
|
|
}
|
|
|
|
static func logAutoAssignUsed(filledSlots: Int, unfilledSlots: Int) {
|
|
logEvent(AnalyticsEvent.autoAssignUsed, parameters: [
|
|
"filled_slots": filledSlots,
|
|
"unfilled_slots": unfilledSlots
|
|
])
|
|
}
|
|
|
|
static func logICloudSyncToggled(enabled: Bool) {
|
|
logEvent(AnalyticsEvent.iCloudSyncToggled, parameters: ["enabled": enabled])
|
|
}
|
|
|
|
static func logNotificationsToggled(enabled: Bool) {
|
|
logEvent(AnalyticsEvent.notificationsToggled, parameters: ["enabled": enabled])
|
|
}
|
|
|
|
static func logStatsViewed() {
|
|
logEvent(AnalyticsEvent.statsViewed)
|
|
}
|
|
|
|
static func logWeekRated(rating: Int) {
|
|
let value = rating == 1 ? "liked" : rating == -1 ? "disliked" : "cleared"
|
|
logEvent(AnalyticsEvent.weekRated, parameters: ["rating": value])
|
|
}
|
|
|
|
static func logPaywallViewed(source: String) {
|
|
logEvent(AnalyticsEvent.paywallViewed, parameters: ["source": source])
|
|
}
|
|
|
|
static func logPaywallDismissed(source: String, secondsOnScreen: Int, didConvert: Bool) {
|
|
logEvent(AnalyticsEvent.paywallDismissed, parameters: [
|
|
"source": source,
|
|
"seconds_on_screen": secondsOnScreen,
|
|
"did_convert": didConvert
|
|
])
|
|
}
|
|
|
|
static func logDishLimitHit() {
|
|
logEvent(AnalyticsEvent.dishLimitHit)
|
|
}
|
|
|
|
static func logWeekLimitHit() {
|
|
logEvent(AnalyticsEvent.weekLimitHit)
|
|
}
|
|
|
|
static func logFreeTrialStarted(plan: String) {
|
|
logEvent(AnalyticsEvent.freeTrialStarted, parameters: ["plan": plan])
|
|
}
|
|
}
|