Replace PostHog integration with AnalyticsManager architecture

Remove old PostHogAnalytics singleton and replace with guide-based
two-file architecture: AnalyticsManager (singleton wrapper with super
properties, session replay, opt-out, subscription funnel) and
AnalyticsEvent (type-safe enum with associated values).

Key changes:
- New API key, self-hosted analytics endpoint
- All 19 events ported to type-safe AnalyticsEvent enum
- Screen tracking via AnalyticsManager.Screen enum + SwiftUI modifier
- Remove all identify() calls — fully anonymous analytics
- Add lifecycle hooks: flush on background, update super properties on foreground
- Add privacy opt-out toggle in Settings
- Subscription funnel methods ready for IAP integration

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-02-11 09:48:49 -06:00
parent 09be5fa444
commit 2fc4a48fc9
50 changed files with 2191 additions and 335 deletions
+8 -11
View File
@@ -221,7 +221,11 @@ struct CompleteTaskView: View {
onRemove: {
withAnimation {
selectedImages.remove(at: index)
selectedItems.remove(at: index)
// Camera photos don't exist in selectedItems.
// Guard the index to avoid out-of-bounds crashes.
if index < selectedItems.count {
selectedItems.remove(at: index)
}
}
}
)
@@ -333,25 +337,19 @@ struct CompleteTaskView: View {
Task {
for await state in completionViewModel.createCompletionState {
await MainActor.run {
switch state {
case let success as ApiResultSuccess<TaskCompletionResponse>:
if let success = state as? ApiResultSuccess<TaskCompletionResponse> {
self.isSubmitting = false
self.onComplete(success.data?.updatedTask) // Pass back updated task
self.dismiss()
case let error as ApiResultError:
} else if let error = ApiResultBridge.error(from: state) {
self.errorMessage = error.message
self.showError = true
self.isSubmitting = false
case is ApiResultLoading:
// Still loading, continue waiting
break
default:
break
}
}
// Break out of loop on terminal states
if state is ApiResultSuccess<TaskCompletionResponse> || state is ApiResultError {
if state is ApiResultSuccess<TaskCompletionResponse> || ApiResultBridge.isError(state) {
break
}
}
@@ -470,4 +468,3 @@ struct ContractorPickerView: View {
}
}
}