Replace EventLogger with typed AnalyticsManager using PostHog

Complete analytics overhaul: delete EventLogger.swift, create Analytics.swift
with typed event enum (~45 events), screen tracking, super properties
(theme, icon pack, voting layout, etc.), session replay with kill switch,
autocapture, and network telemetry. Replace all 99 call sites across 38 files
with compiler-enforced typed events in object_action naming convention.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-10 15:12:33 -06:00
parent a08d0d33c0
commit e0330dbc8d
38 changed files with 1048 additions and 202 deletions

View File

@@ -106,7 +106,7 @@ struct CustomizeContentView: View {
.padding(.bottom, 32)
}
.onAppear(perform: {
EventLogger.log(event: "show_customize_view")
AnalyticsManager.shared.trackScreen(.customize)
})
.customizeLayoutTip()
}
@@ -175,10 +175,10 @@ struct CustomizeView: View {
.padding(.bottom, 32)
}
.onAppear(perform: {
EventLogger.log(event: "show_customize_view")
AnalyticsManager.shared.trackScreen(.customize)
})
.sheet(isPresented: $showSubscriptionStore) {
FeelsSubscriptionStoreView()
FeelsSubscriptionStoreView(source: "customize")
}
.background(
theme.currentTheme.bg
@@ -253,7 +253,7 @@ struct ThemePickerCompact: View {
ForEach(Theme.allCases, id: \.rawValue) { aTheme in
Button(action: {
theme = aTheme
EventLogger.log(event: "change_theme_id", withData: ["id": aTheme.rawValue])
AnalyticsManager.shared.track(.themeChanged(themeId: aTheme.rawValue))
}) {
VStack(spacing: 8) {
ZStack {
@@ -300,7 +300,7 @@ struct ImagePackPickerCompact: View {
let impactMed = UIImpactFeedbackGenerator(style: .medium)
impactMed.impactOccurred()
imagePack = images
EventLogger.log(event: "change_image_pack_id", withData: ["id": images.rawValue])
AnalyticsManager.shared.track(.iconPackChanged(packId: images.rawValue))
}) {
HStack {
HStack(spacing: 16) {
@@ -358,7 +358,7 @@ struct VotingLayoutPickerCompact: View {
votingLayoutStyle = layout.rawValue
}
}
EventLogger.log(event: "change_voting_layout", withData: ["layout": layout.displayName])
AnalyticsManager.shared.track(.votingLayoutChanged(layout: layout.displayName))
}) {
VStack(spacing: 6) {
layoutIcon(for: layout)
@@ -490,7 +490,7 @@ struct CustomWidgetSection: View {
.frame(width: 60, height: 60)
.cornerRadius(12)
.onTapGesture {
EventLogger.log(event: "show_widget")
AnalyticsManager.shared.track(.widgetViewed)
selectedWidget.selectedItem = widget.copy() as? CustomWidgetModel
selectedWidget.showSheet = true
}
@@ -498,7 +498,7 @@ struct CustomWidgetSection: View {
// Add button
Button(action: {
EventLogger.log(event: "tap_create_new_widget")
AnalyticsManager.shared.track(.widgetCreateTapped)
selectedWidget.selectedItem = CustomWidgetModel.randomWidget
selectedWidget.showSheet = true
}) {
@@ -547,12 +547,11 @@ struct PersonalityPackPickerCompact: View {
Button(action: {
// if aPack.rawValue == PersonalityPack.Rude.rawValue && !showNSFW {
// showOver18Alert = true
// EventLogger.log(event: "show_over_18_alert")
// } else {
let impactMed = UIImpactFeedbackGenerator(style: .medium)
impactMed.impactOccurred()
personalityPack = aPack
EventLogger.log(event: "change_personality_pack", withData: ["pack_title": aPack.title()])
AnalyticsManager.shared.track(.personalityPackChanged(packTitle: aPack.title()))
LocalNotification.rescheduleNotifiations()
// }
}) {
@@ -651,7 +650,7 @@ struct SubscriptionBannerView: View {
private var notSubscribedView: some View {
Button(action: {
EventLogger.log(event: "customize_subscribe_tapped")
AnalyticsManager.shared.track(.paywallSubscribeTapped(source: "customize"))
showSubscriptionStore = true
}) {
HStack(spacing: 12) {
@@ -722,7 +721,7 @@ struct DayViewStylePickerCompact: View {
}
let impactMed = UIImpactFeedbackGenerator(style: .medium)
impactMed.impactOccurred()
EventLogger.log(event: "change_day_view_style", withData: ["style": style.displayName])
AnalyticsManager.shared.track(.dayViewStyleChanged(style: style.displayName))
}) {
VStack(spacing: 6) {
styleIcon(for: style)

View File

@@ -24,7 +24,7 @@ struct CustomWigetView: View {
.frame(width: 50, height: 50)
.cornerRadius(10)
.onTapGesture {
EventLogger.log(event: "show_widget")
AnalyticsManager.shared.track(.widgetViewed)
selectedWidget.selectedItem = widget.copy() as? CustomWidgetModel
selectedWidget.showSheet = true
}
@@ -35,7 +35,7 @@ struct CustomWigetView: View {
Image(systemName: "plus")
)
.onTapGesture {
EventLogger.log(event: "tap_create_new_widget")
AnalyticsManager.shared.track(.widgetCreateTapped)
selectedWidget.selectedItem = CustomWidgetModel.randomWidget
selectedWidget.showSheet = true
}

View File

@@ -57,7 +57,7 @@ struct IconPickerView: View {
HStack {
Button(action: {
UIApplication.shared.setAlternateIconName(nil)
EventLogger.log(event: "change_icon_title", withData: ["title": "default"])
AnalyticsManager.shared.track(.appIconChanged(iconTitle: "default"))
}, label: {
Image("AppIconImage", bundle: .main)
.resizable()
@@ -73,7 +73,7 @@ struct IconPickerView: View {
UIApplication.shared.setAlternateIconName(iconSet.1) { (error) in
// FIXME: Handle error
}
EventLogger.log(event: "change_icon_title", withData: ["title": iconSet.1])
AnalyticsManager.shared.track(.appIconChanged(iconTitle: iconSet.1))
}, label: {
Image(iconSet.0, bundle: .main)
.resizable()

View File

@@ -45,7 +45,7 @@ struct ImagePackPickerView: View {
let impactMed = UIImpactFeedbackGenerator(style: .heavy)
impactMed.impactOccurred()
imagePack = images
EventLogger.log(event: "change_image_pack_id", withData: ["id": images.rawValue])
AnalyticsManager.shared.track(.iconPackChanged(packId: images.rawValue))
}
if images.rawValue != (MoodImages.allCases.sorted(by: { $0.rawValue > $1.rawValue }).first?.rawValue) ?? 0 {
Divider()

View File

@@ -41,14 +41,10 @@ struct PersonalityPackPickerView: View {
.padding(5)
)
.onTapGesture {
// if aPack.rawValue == PersonalityPack.Rude.rawValue && !showNSFW {
// showOver18Alert = true
// EventLogger.log(event: "show_over_18_alert")
// } else {
let impactMed = UIImpactFeedbackGenerator(style: .heavy)
impactMed.impactOccurred()
personalityPack = aPack
EventLogger.log(event: "change_personality_pack", withData: ["pack_title": aPack.title()])
AnalyticsManager.shared.track(.personalityPackChanged(packTitle: aPack.title()))
LocalNotification.rescheduleNotifiations()
// }
}

View File

@@ -47,7 +47,7 @@ struct ShapePickerView: View {
let impactMed = UIImpactFeedbackGenerator(style: .heavy)
impactMed.impactOccurred()
shape = ashape
EventLogger.log(event: "change_mood_shape_id", withData: ["id": shape.rawValue])
AnalyticsManager.shared.track(.moodShapeChanged(shapeId: shape.rawValue))
}
.contentShape(Rectangle())
.background(

View File

@@ -71,7 +71,7 @@ struct ThemePickerView: View {
selectedTheme = theme
}
EventLogger.log(event: "change_theme_id", withData: ["id": theme.rawValue])
AnalyticsManager.shared.track(.themeChanged(themeId: theme.rawValue))
}
}

View File

@@ -39,7 +39,7 @@ struct VotingLayoutPickerView: View {
votingLayoutStyle = layout.rawValue
}
}
EventLogger.log(event: "change_voting_layout", withData: ["layout": layout.displayName])
AnalyticsManager.shared.track(.votingLayoutChanged(layout: layout.displayName))
}) {
VStack(spacing: 6) {
layoutIcon(for: layout)