- Add mapItem field to POISearchService.POI for Apple Maps integration - Merge description + location into single combined card in QuickAddItemSheet - Auto-load nearby POIs when regionCoordinate is available, with detail sheet - Create POIDetailSheet with map preview, metadata, and one-tap add-to-day - Add poiAddedToDay/poiDetailViewed analytics events - Add initial state to PlaceSearchSheet with search suggestions and flow layout Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
272 lines
9.4 KiB
Swift
272 lines
9.4 KiB
Swift
//
|
|
// AnalyticsEvent.swift
|
|
// SportsTime
|
|
//
|
|
// Type-safe analytics event definitions.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
enum AnalyticsEvent {
|
|
|
|
// MARK: - Navigation
|
|
|
|
case tabSwitched(tab: String, previousTab: String?)
|
|
case screenViewed(screen: String)
|
|
|
|
// MARK: - Trip Planning
|
|
|
|
case tripWizardStarted(mode: String)
|
|
case tripWizardStepCompleted(step: String, mode: String)
|
|
case tripPlanned(sportCount: Int, stopCount: Int, dayCount: Int, mode: String)
|
|
case tripPlanFailed(mode: String, error: String)
|
|
case tripSaved(tripId: String, stopCount: Int, gameCount: Int)
|
|
case tripDeleted(tripId: String)
|
|
case tripViewed(tripId: String, source: String)
|
|
case suggestedTripTapped(region: String, stopCount: Int)
|
|
|
|
// MARK: - Schedule
|
|
|
|
case scheduleViewed(sports: [String])
|
|
case scheduleFiltered(sport: String, dateRange: String)
|
|
case gameTapped(gameId: String, sport: String, homeTeam: String, awayTeam: String)
|
|
|
|
// MARK: - Progress
|
|
|
|
case stadiumVisitAdded(stadiumId: String, sport: String)
|
|
case stadiumVisitDeleted(stadiumId: String, sport: String)
|
|
case progressCardShared(sport: String)
|
|
case sportSwitched(sport: String)
|
|
|
|
// MARK: - Export
|
|
|
|
case pdfExportStarted(tripId: String, stopCount: Int)
|
|
case pdfExportCompleted(tripId: String)
|
|
case pdfExportFailed(tripId: String, error: String)
|
|
case tripShared(tripId: String)
|
|
|
|
// MARK: - IAP
|
|
|
|
case paywallViewed(source: String)
|
|
case purchaseStarted(productId: String)
|
|
case purchaseCompleted(productId: String)
|
|
case purchaseFailed(productId: String, error: String)
|
|
case purchaseRestored
|
|
case subscriptionStatusChanged(isPro: Bool, plan: String?)
|
|
|
|
// MARK: - Settings
|
|
|
|
case themeChanged(from: String, to: String)
|
|
case appearanceChanged(mode: String)
|
|
case sportToggled(sport: String, enabled: Bool)
|
|
case animationsToggled(enabled: Bool)
|
|
case drivingHoursChanged(hours: Int)
|
|
case analyticsToggled(enabled: Bool)
|
|
case settingsReset
|
|
|
|
// MARK: - Polls
|
|
|
|
case pollCreated(optionCount: Int)
|
|
case pollVoted(pollId: String)
|
|
case pollShared(pollId: String)
|
|
|
|
// MARK: - Onboarding
|
|
|
|
case onboardingPaywallViewed
|
|
case onboardingPaywallDismissed
|
|
|
|
// MARK: - POI
|
|
|
|
case poiAddedToDay(poiName: String, category: String, day: Int)
|
|
case poiDetailViewed(poiName: String, category: String)
|
|
|
|
// MARK: - Errors
|
|
|
|
case errorOccurred(domain: String, message: String, screen: String?)
|
|
|
|
// MARK: - Computed Properties
|
|
|
|
var name: String {
|
|
switch self {
|
|
case .tabSwitched: return "tab_switched"
|
|
case .screenViewed: return "screen_viewed"
|
|
case .tripWizardStarted: return "trip_wizard_started"
|
|
case .tripWizardStepCompleted: return "trip_wizard_step_completed"
|
|
case .tripPlanned: return "trip_planned"
|
|
case .tripPlanFailed: return "trip_plan_failed"
|
|
case .tripSaved: return "trip_saved"
|
|
case .tripDeleted: return "trip_deleted"
|
|
case .tripViewed: return "trip_viewed"
|
|
case .suggestedTripTapped: return "suggested_trip_tapped"
|
|
case .scheduleViewed: return "schedule_viewed"
|
|
case .scheduleFiltered: return "schedule_filtered"
|
|
case .gameTapped: return "game_tapped"
|
|
case .stadiumVisitAdded: return "stadium_visit_added"
|
|
case .stadiumVisitDeleted: return "stadium_visit_deleted"
|
|
case .progressCardShared: return "progress_card_shared"
|
|
case .sportSwitched: return "sport_switched"
|
|
case .pdfExportStarted: return "pdf_export_started"
|
|
case .pdfExportCompleted: return "pdf_export_completed"
|
|
case .pdfExportFailed: return "pdf_export_failed"
|
|
case .tripShared: return "trip_shared"
|
|
case .paywallViewed: return "paywall_viewed"
|
|
case .purchaseStarted: return "purchase_started"
|
|
case .purchaseCompleted: return "purchase_completed"
|
|
case .purchaseFailed: return "purchase_failed"
|
|
case .purchaseRestored: return "purchase_restored"
|
|
case .subscriptionStatusChanged: return "subscription_status_changed"
|
|
case .themeChanged: return "theme_changed"
|
|
case .appearanceChanged: return "appearance_changed"
|
|
case .sportToggled: return "sport_toggled"
|
|
case .animationsToggled: return "animations_toggled"
|
|
case .drivingHoursChanged: return "driving_hours_changed"
|
|
case .analyticsToggled: return "analytics_toggled"
|
|
case .settingsReset: return "settings_reset"
|
|
case .pollCreated: return "poll_created"
|
|
case .pollVoted: return "poll_voted"
|
|
case .pollShared: return "poll_shared"
|
|
case .onboardingPaywallViewed: return "onboarding_paywall_viewed"
|
|
case .onboardingPaywallDismissed: return "onboarding_paywall_dismissed"
|
|
case .poiAddedToDay: return "poi_added_to_day"
|
|
case .poiDetailViewed: return "poi_detail_viewed"
|
|
case .errorOccurred: return "error_occurred"
|
|
}
|
|
}
|
|
|
|
var properties: [String: Any] {
|
|
switch self {
|
|
case .tabSwitched(let tab, let previousTab):
|
|
var props: [String: Any] = ["tab_name": tab]
|
|
if let prev = previousTab { props["previous_tab"] = prev }
|
|
return props
|
|
|
|
case .screenViewed(let screen):
|
|
return ["screen_name": screen]
|
|
|
|
case .tripWizardStarted(let mode):
|
|
return ["mode": mode]
|
|
|
|
case .tripWizardStepCompleted(let step, let mode):
|
|
return ["step_name": step, "mode": mode]
|
|
|
|
case .tripPlanned(let sportCount, let stopCount, let dayCount, let mode):
|
|
return ["sport_count": sportCount, "stop_count": stopCount, "day_count": dayCount, "mode": mode]
|
|
|
|
case .tripPlanFailed(let mode, let error):
|
|
return ["mode": mode, "error": error]
|
|
|
|
case .tripSaved(let tripId, let stopCount, let gameCount):
|
|
return ["trip_id": tripId, "stop_count": stopCount, "game_count": gameCount]
|
|
|
|
case .tripDeleted(let tripId):
|
|
return ["trip_id": tripId]
|
|
|
|
case .tripViewed(let tripId, let source):
|
|
return ["trip_id": tripId, "source": source]
|
|
|
|
case .suggestedTripTapped(let region, let stopCount):
|
|
return ["region": region, "stop_count": stopCount]
|
|
|
|
case .scheduleViewed(let sports):
|
|
return ["sports": sports]
|
|
|
|
case .scheduleFiltered(let sport, let dateRange):
|
|
return ["sport": sport, "date_range": dateRange]
|
|
|
|
case .gameTapped(let gameId, let sport, let homeTeam, let awayTeam):
|
|
return ["game_id": gameId, "sport": sport, "home_team": homeTeam, "away_team": awayTeam]
|
|
|
|
case .stadiumVisitAdded(let stadiumId, let sport):
|
|
return ["stadium_id": stadiumId, "sport": sport]
|
|
|
|
case .stadiumVisitDeleted(let stadiumId, let sport):
|
|
return ["stadium_id": stadiumId, "sport": sport]
|
|
|
|
case .progressCardShared(let sport):
|
|
return ["sport": sport]
|
|
|
|
case .sportSwitched(let sport):
|
|
return ["sport": sport]
|
|
|
|
case .pdfExportStarted(let tripId, let stopCount):
|
|
return ["trip_id": tripId, "stop_count": stopCount]
|
|
|
|
case .pdfExportCompleted(let tripId):
|
|
return ["trip_id": tripId]
|
|
|
|
case .pdfExportFailed(let tripId, let error):
|
|
return ["trip_id": tripId, "error": error]
|
|
|
|
case .tripShared(let tripId):
|
|
return ["trip_id": tripId]
|
|
|
|
case .paywallViewed(let source):
|
|
return ["source": source]
|
|
|
|
case .purchaseStarted(let productId):
|
|
return ["product_id": productId]
|
|
|
|
case .purchaseCompleted(let productId):
|
|
return ["product_id": productId]
|
|
|
|
case .purchaseFailed(let productId, let error):
|
|
return ["product_id": productId, "error": error]
|
|
|
|
case .purchaseRestored:
|
|
return [:]
|
|
|
|
case .subscriptionStatusChanged(let isPro, let plan):
|
|
var props: [String: Any] = ["is_pro": isPro]
|
|
if let plan { props["plan"] = plan }
|
|
return props
|
|
|
|
case .themeChanged(let from, let to):
|
|
return ["from": from, "to": to]
|
|
|
|
case .appearanceChanged(let mode):
|
|
return ["mode": mode]
|
|
|
|
case .sportToggled(let sport, let enabled):
|
|
return ["sport": sport, "enabled": enabled]
|
|
|
|
case .animationsToggled(let enabled):
|
|
return ["enabled": enabled]
|
|
|
|
case .drivingHoursChanged(let hours):
|
|
return ["hours": hours]
|
|
|
|
case .analyticsToggled(let enabled):
|
|
return ["enabled": enabled]
|
|
|
|
case .settingsReset:
|
|
return [:]
|
|
|
|
case .pollCreated(let optionCount):
|
|
return ["option_count": optionCount]
|
|
|
|
case .pollVoted(let pollId):
|
|
return ["poll_id": pollId]
|
|
|
|
case .pollShared(let pollId):
|
|
return ["poll_id": pollId]
|
|
|
|
case .onboardingPaywallViewed:
|
|
return [:]
|
|
|
|
case .onboardingPaywallDismissed:
|
|
return [:]
|
|
|
|
case .poiAddedToDay(let poiName, let category, let day):
|
|
return ["poi_name": poiName, "category": category, "day": day]
|
|
|
|
case .poiDetailViewed(let poiName, let category):
|
|
return ["poi_name": poiName, "category": category]
|
|
|
|
case .errorOccurred(let domain, let message, let screen):
|
|
var props: [String: Any] = ["domain": domain, "message": message]
|
|
if let screen { props["screen"] = screen }
|
|
return props
|
|
}
|
|
}
|
|
}
|