From 2e57871793de6ca4dd27dffaf183d1cfe4891803 Mon Sep 17 00:00:00 2001 From: Owen Gretzinger Date: Sun, 13 Jul 2025 20:38:18 -0400 Subject: [PATCH] feat: anonymous analytics with PostHog (#3) --- Meetingnotes.xcodeproj/project.pbxproj | 17 +++++++++++++++++ .../xcshareddata/swiftpm/Package.resolved | 11 ++++++++++- README.md | 3 ++- meetingnotes/MeetingnotesApp.swift | 17 +++++++++++++++++ .../ViewModels/MeetingListViewModel.swift | 3 +++ meetingnotes/ViewModels/MeetingViewModel.swift | 1 + meetingnotes/Views/OnboardingView.swift | 2 +- 7 files changed, 51 insertions(+), 3 deletions(-) diff --git a/Meetingnotes.xcodeproj/project.pbxproj b/Meetingnotes.xcodeproj/project.pbxproj index 8dbc229..e54cdbf 100644 --- a/Meetingnotes.xcodeproj/project.pbxproj +++ b/Meetingnotes.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ CC5718382E2020490042BA88 /* OpenAI in Frameworks */ = {isa = PBXBuildFile; productRef = CC5718372E2020490042BA88 /* OpenAI */; }; CCBA53E72E212F1C00469D84 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = CCBA53E62E212F1C00469D84 /* Sparkle */; }; + CCC33F812E236B6F00EDE382 /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = CCC33F802E236B6F00EDE382 /* PostHog */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -28,6 +29,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + CCC33F812E236B6F00EDE382 /* PostHog in Frameworks */, CC5718382E2020490042BA88 /* OpenAI in Frameworks */, CCBA53E72E212F1C00469D84 /* Sparkle in Frameworks */, ); @@ -82,6 +84,7 @@ packageProductDependencies = ( CC5718372E2020490042BA88 /* OpenAI */, CCBA53E62E212F1C00469D84 /* Sparkle */, + CCC33F802E236B6F00EDE382 /* PostHog */, ); productName = Meetingnotes; productReference = CC5718182E201D8D0042BA88 /* Meetingnotes.app */; @@ -114,6 +117,7 @@ packageReferences = ( CC5718362E2020490042BA88 /* XCRemoteSwiftPackageReference "OpenAI" */, CCBA53E52E212F1C00469D84 /* XCRemoteSwiftPackageReference "Sparkle" */, + CCC33F7F2E236B6F00EDE382 /* XCRemoteSwiftPackageReference "posthog-ios" */, ); preferredProjectObjectVersion = 77; productRefGroup = CC5718192E201D8D0042BA88 /* Products */; @@ -373,6 +377,14 @@ minimumVersion = 2.7.1; }; }; + CCC33F7F2E236B6F00EDE382 /* XCRemoteSwiftPackageReference "posthog-ios" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/PostHog/posthog-ios.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 3.28.2; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -386,6 +398,11 @@ package = CCBA53E52E212F1C00469D84 /* XCRemoteSwiftPackageReference "Sparkle" */; productName = Sparkle; }; + CCC33F802E236B6F00EDE382 /* PostHog */ = { + isa = XCSwiftPackageProductDependency; + package = CCC33F7F2E236B6F00EDE382 /* XCRemoteSwiftPackageReference "posthog-ios" */; + productName = PostHog; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = CC5718102E201D8D0042BA88 /* Project object */; diff --git a/Meetingnotes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Meetingnotes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e76a851..ac5792c 100644 --- a/Meetingnotes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Meetingnotes.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "e8774f1c4a54f0b9bb959e13d276fae95c54472a479606f1fd28800bfbcb6da5", + "originHash" : "55d8e28deb3208ddf2aa2ec08d05566b9cae31d15d7c0ac6f9194b6eea1a9a74", "pins" : [ { "identity" : "openai", @@ -10,6 +10,15 @@ "version" : "0.4.4" } }, + { + "identity" : "posthog-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/PostHog/posthog-ios.git", + "state" : { + "revision" : "784145ddd2f7de3a6d6fcce0e50c04f29d33b338", + "version" : "3.28.2" + } + }, { "identity" : "sparkle", "kind" : "remoteSourceControl", diff --git a/README.md b/README.md index 00e8112..00a8152 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,11 @@ Implemented: - Auto updates - Text formatting - Different note templates +- Integrate with Posthog for anonymous analytics (installs, opens, meetings created) Todo: -- Integrate with Posthog for anonymous analytics +- Onboarding screen to enable settings and set API key Later: diff --git a/meetingnotes/MeetingnotesApp.swift b/meetingnotes/MeetingnotesApp.swift index 740a289..3aed1c5 100644 --- a/meetingnotes/MeetingnotesApp.swift +++ b/meetingnotes/MeetingnotesApp.swift @@ -7,6 +7,7 @@ import SwiftUI import Sparkle +import PostHog @main struct MeetingnotesApp: App { @@ -14,6 +15,22 @@ struct MeetingnotesApp: App { init() { updaterController = SPUStandardUpdaterController(updaterDelegate: nil, userDriverDelegate: nil) + // Setup PostHog analytics for anonymous tracking + let posthogAPIKey = "phc_Wt8sWUzUF7YPF50aQ0B1qbfA5SJWWR341zmXCaIaIRJ" + let posthogHost = "https://us.i.posthog.com" + let config = PostHogConfig(apiKey: posthogAPIKey, host: posthogHost) + // Only capture anonymous events + config.personProfiles = .never + // Enable lifecycle and screen view autocapture + config.captureApplicationLifecycleEvents = true + config.captureScreenViews = true + PostHogSDK.shared.setup(config) + // Register environment as a super property + #if DEBUG + PostHogSDK.shared.register(["environment": "dev"] ) + #else + PostHogSDK.shared.register(["environment": "prod"] ) + #endif } var body: some Scene { diff --git a/meetingnotes/ViewModels/MeetingListViewModel.swift b/meetingnotes/ViewModels/MeetingListViewModel.swift index 0346c3b..549a979 100644 --- a/meetingnotes/ViewModels/MeetingListViewModel.swift +++ b/meetingnotes/ViewModels/MeetingListViewModel.swift @@ -1,6 +1,7 @@ import Foundation import SwiftUI import Combine +import PostHog @MainActor class MeetingListViewModel: ObservableObject { @@ -69,6 +70,8 @@ class MeetingListViewModel: ObservableObject { let newMeeting = Meeting() meetings.insert(newMeeting, at: 0) _ = LocalStorageManager.shared.saveMeeting(newMeeting) + // Track meeting creation event + PostHogSDK.shared.capture("meeting_created") return newMeeting } } \ No newline at end of file diff --git a/meetingnotes/ViewModels/MeetingViewModel.swift b/meetingnotes/ViewModels/MeetingViewModel.swift index 47971e8..f43ceff 100644 --- a/meetingnotes/ViewModels/MeetingViewModel.swift +++ b/meetingnotes/ViewModels/MeetingViewModel.swift @@ -1,6 +1,7 @@ import Foundation import SwiftUI import Combine +import PostHog // Add notification name for meeting saved events extension Notification.Name { diff --git a/meetingnotes/Views/OnboardingView.swift b/meetingnotes/Views/OnboardingView.swift index 3c3b3a8..ba07beb 100644 --- a/meetingnotes/Views/OnboardingView.swift +++ b/meetingnotes/Views/OnboardingView.swift @@ -80,7 +80,7 @@ struct OnboardingView: View { .font(.title2) .fontWeight(.semibold) - Text("Please review and accept the terms of service and privacy policy.") + Text("Of note, I am tracking a few basic anonymous metrics (installs, opens, meetings created) using PostHog, because I'm trying to gauge interest in the app and whether it's worth it to pay for an Apple Developer License & domain name. Everything is completely anonymous (feel free to check the source code). If you have any concerns, please reach out.") .font(.body) .foregroundColor(.secondary) }