feat: add PostHog analytics with full event tracking across app

Integrate self-hosted PostHog (SPM) with AnalyticsManager singleton wrapping
all SDK calls. Adds ~40 type-safe events covering trip planning, schedule,
progress, IAP, settings, polls, export, and share flows. Includes session
replay, autocapture, network telemetry, privacy opt-out toggle in Settings,
and super properties (app version, device, pro status, selected sports).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-10 15:12:16 -06:00
parent 5389fe3759
commit 2917ae22b1
20 changed files with 989 additions and 23 deletions

View File

@@ -116,6 +116,7 @@ struct TripDetailView: View {
Text("This trip has \(multiRouteChunks.flatMap { $0 }.count) stops, which exceeds Apple Maps' limit of 16. Open the route in parts?")
}
.onAppear {
AnalyticsManager.shared.track(.tripViewed(tripId: trip.id.uuidString, source: allowCustomItems ? "saved" : "new"))
checkIfSaved()
// Demo mode: auto-favorite the trip
if isDemoMode && !hasAppliedDemoSelection && !isSaved {
@@ -1203,6 +1204,7 @@ struct TripDetailView: View {
private func exportPDF() async {
isExporting = true
exportProgress = nil
AnalyticsManager.shared.track(.pdfExportStarted(tripId: trip.id.uuidString, stopCount: trip.stops.count))
do {
// Build complete itinerary items (games + travel + custom)
@@ -1219,8 +1221,9 @@ struct TripDetailView: View {
}
exportURL = url
showExportSheet = true
AnalyticsManager.shared.track(.pdfExportCompleted(tripId: trip.id.uuidString))
} catch {
// PDF export failed silently
AnalyticsManager.shared.track(.pdfExportFailed(tripId: trip.id.uuidString, error: error.localizedDescription))
}
isExporting = false
@@ -1323,6 +1326,11 @@ struct TripDetailView: View {
withAnimation(.spring(response: 0.3, dampingFraction: 0.6)) {
isSaved = true
}
AnalyticsManager.shared.track(.tripSaved(
tripId: trip.id.uuidString,
stopCount: trip.stops.count,
gameCount: trip.totalGames
))
} catch {
// Save failed silently
}
@@ -1343,6 +1351,7 @@ struct TripDetailView: View {
withAnimation(.spring(response: 0.3, dampingFraction: 0.6)) {
isSaved = false
}
AnalyticsManager.shared.track(.tripDeleted(tripId: tripId.uuidString))
} catch {
// Unsave failed silently
}
@@ -2047,7 +2056,7 @@ private struct SheetModifiers: ViewModifier {
}
}
.sheet(isPresented: $showProPaywall) {
PaywallView()
PaywallView(source: "trip_detail")
}
.sheet(item: $addItemAnchor) { anchor in
QuickAddItemSheet(