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" static let weekCopiedPrevious = "week_copied_previous" // Shopping list (2.0) static let shoppingListOpened = "shopping_list_opened" static let shoppingItemAdded = "shopping_item_added" static let ingredientsGenerated = "ingredients_generated" // 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, source: String = "editor") { logEvent(AnalyticsEvent.dishAdded, parameters: [ "tag_count": tagCount, "source": source ]) } 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(environment: String = "unknown") { logEvent(AnalyticsEvent.premiumPurchased, parameters: ["environment": environment]) } static func logPremiumRestored() { logEvent(AnalyticsEvent.premiumRestored) } static func logOnboardingCompleted(dishCount: Int) { logEvent(AnalyticsEvent.onboardingCompleted, parameters: ["dish_count": dishCount]) } // Human-readable names for each onboarding step, indexed by step number. // Keep in sync with the OnboardingView step order. private static let onboardingStepNames = [ "0_welcome", "1_meal_windows", "2_weekends", "3_calendar", "4_first_dishes", "5_paywall" ] static func logOnboardingStepViewed(step: Int) { // GA4 text custom dimensions do not capture numeric params (they show as // "(not set)"). Log the step as a String so the drop-off is queryable. let name = step >= 0 && step < onboardingStepNames.count ? onboardingStepNames[step] : "\(step)" logEvent(AnalyticsEvent.onboardingStepViewed, parameters: [ "step": "\(step)", "step_name": name ]) } static func logAutoAssignUsed(filledSlots: Int, unfilledSlots: Int) { logEvent(AnalyticsEvent.autoAssignUsed, parameters: [ "filled_slots": filledSlots, "unfilled_slots": unfilledSlots ]) } static func logWeekCopiedPrevious(copiedSlots: Int, source: String) { logEvent(AnalyticsEvent.weekCopiedPrevious, parameters: [ "copied_slots": copiedSlots, "source": source ]) } static func logShoppingListOpened(itemCount: Int, missingDishes: Int) { logEvent(AnalyticsEvent.shoppingListOpened, parameters: [ "item_count": itemCount, "missing_dishes": missingDishes ]) } static func logShoppingItemAdded() { logEvent(AnalyticsEvent.shoppingItemAdded) } static func logIngredientsGenerated(lineCount: Int, source: String) { logEvent(AnalyticsEvent.ingredientsGenerated, parameters: [ "line_count": lineCount, "source": source ]) } 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]) } }