Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# PostHog public client configuration
POSTHOG_PROJECT_TOKEN=phc_your_project_token
POSTHOG_HOST=https://your-posthog-host
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Environment
.env
.env.xcconfig

# Xcode
MicStatusAI.xcodeproj/
DerivedData/
Expand Down
35 changes: 35 additions & 0 deletions MicStatusAI/App/MicStatusAIApp.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,42 @@
import PostHog
import SwiftUI

@main
struct MicStatusAIApp: App {
init() {
let environment = ProcessInfo.processInfo.environment
let projectToken = environment["POSTHOG_PROJECT_TOKEN"]
?? Bundle.main.object(forInfoDictionaryKey: "POSTHOG_PROJECT_TOKEN") as? String
guard let projectToken, !projectToken.isEmpty else {
#if DEBUG
fatalError(
"POSTHOG_PROJECT_TOKEN variable required by PostHog is missing or un-configured, "
+ "this causes events to be silently missed. This error stops appearing once "
+ "POSTHOG_PROJECT_TOKEN is configured"
)
#else
return
#endif
}

let host = environment["POSTHOG_HOST"]
?? Bundle.main.object(forInfoDictionaryKey: "POSTHOG_HOST") as? String
guard let host, !host.isEmpty else {
#if DEBUG
fatalError(
"POSTHOG_HOST variable required by PostHog is missing or un-configured, this causes "
+ "events to be silently missed. This error stops appearing once POSTHOG_HOST is configured"
)
#else
return
#endif
}

let config = PostHogConfig(apiKey: projectToken, host: host)
config.errorTrackingConfig.autoCapture = true
PostHogSDK.shared.setup(config)
}

@AppStorage("statusOverlayEnabled")
private var statusOverlayEnabled = true
@AppStorage("statusOverlayDuration")
Expand Down
5 changes: 5 additions & 0 deletions MicStatusAI/Coordinators/HotKeyRecorderCoordinator.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import AppKit
import Carbon.HIToolbox
import PostHog

@MainActor
final class HotKeyRecorderCoordinator: NSObject {
Expand Down Expand Up @@ -47,6 +48,10 @@ final class HotKeyRecorderCoordinator: NSObject {

parent.onValidationError(nil)
parent.onChange(configuration)
PostHogSDK.shared.capture(
"hotkey_configured",
properties: ["modifier_count": configuration.modifierCount]
)
button.finishRecording(with: configuration)
button.window?.makeFirstResponder(nil)
}
Expand Down
4 changes: 4 additions & 0 deletions MicStatusAI/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,9 @@
<true/>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>POSTHOG_HOST</key>
<string>$(POSTHOG_HOST)</string>
<key>POSTHOG_PROJECT_TOKEN</key>
<string>$(POSTHOG_PROJECT_TOKEN)</string>
</dict>
</plist>
11 changes: 11 additions & 0 deletions MicStatusAI/Models/MicrophoneStatusModel.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation
import Observation
import PostHog

@MainActor
@Observable
Expand Down Expand Up @@ -79,6 +80,11 @@ final class MicrophoneStatusModel {
} else {
startMonitoring()
}

PostHogSDK.shared.capture(
"microphone_monitoring_toggled",
properties: ["is_monitoring": isMonitoring]
)
}

func startMonitoring() {
Expand Down Expand Up @@ -142,6 +148,11 @@ final class MicrophoneStatusModel {
if isMonitoring {
refreshStatus()
}

PostHogSDK.shared.capture(
"microphone_mute_toggled",
properties: ["is_muted": targetMuteState]
)
} catch {
if isMonitoring {
status = .unavailable(error.localizedDescription)
Expand Down
2 changes: 2 additions & 0 deletions MicStatusAI/Views/HotKeySettingsView.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import PostHog
import SwiftUI

struct HotKeySettingsView: View {
Expand Down Expand Up @@ -69,6 +70,7 @@ struct HotKeySettingsView: View {
Button {
recordingError = nil
model.restoreDefaultHotKey()
PostHogSDK.shared.capture("hotkey_restored")
} label: {
Text(L10n.actionRestoreHotkey)
}
Expand Down
17 changes: 13 additions & 4 deletions MicStatusAI/Views/InputLevelControl.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import PostHog
import SwiftUI

struct InputLevelControl: View {
Expand All @@ -15,10 +16,18 @@ struct InputLevelControl: View {
get: { model.inputLevel },
set: { model.setInputLevel($0) }
),
in: 0 ... 1
) {
Text(L10n.inputAccessibility)
}
in: 0 ... 1,
onEditingChanged: { isEditing in
guard !isEditing else { return }
PostHogSDK.shared.capture(
"input_level_adjusted",
properties: ["input_level": model.inputLevel]
)
},
label: {
Text(L10n.inputAccessibility)
}
)
.tint(model.inputLevel > 0 ? .green : .red)
.disabled(!model.canAdjustInputLevel)
.accessibilityValue(
Expand Down
30 changes: 26 additions & 4 deletions MicStatusAI/Views/StatusOverlaySettingsView.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import PostHog
import SwiftUI

struct StatusOverlaySettingsView: View {
Expand All @@ -10,6 +11,9 @@ struct StatusOverlaySettingsView: View {
GroupBox {
VStack(alignment: .leading, spacing: 8) {
Toggle(L10n.overlayEnabled, isOn: $isEnabled)
.onChange(of: isEnabled) { _, isEnabled in
captureSettingChange("enabled", value: isEnabled)
}

Picker(L10n.overlayDuration, selection: $duration) {
ForEach(StatusOverlayDuration.allCases) { option in
Expand All @@ -19,6 +23,9 @@ struct StatusOverlaySettingsView: View {
}
.pickerStyle(.menu)
.disabled(!isEnabled)
.onChange(of: duration) { _, duration in
captureSettingChange("duration_seconds", value: duration.rawValue)
}

Picker(L10n.overlayPlacement, selection: $placement) {
ForEach(StatusOverlayPlacement.allCases) { option in
Expand All @@ -28,16 +35,24 @@ struct StatusOverlaySettingsView: View {
}
.pickerStyle(.menu)
.disabled(!isEnabled)
.onChange(of: placement) { _, placement in
captureSettingChange("placement", value: placement.rawValue)
}

LabeledContent {
HStack(spacing: 8) {
Slider(
value: $transparency,
in: StatusOverlayTransparency.range,
step: StatusOverlayTransparency.step
) {
Text(L10n.overlayTransparency)
}
step: StatusOverlayTransparency.step,
onEditingChanged: { isEditing in
guard !isEditing else { return }
captureSettingChange("transparency", value: transparency)
},
label: {
Text(L10n.overlayTransparency)
}
)
.labelsHidden()
.accessibilityLabel(L10n.overlayTransparency)
.accessibilityValue(
Expand Down Expand Up @@ -70,4 +85,11 @@ struct StatusOverlaySettingsView: View {
Text(L10n.overlayTitle)
}
}

private func captureSettingChange(_ setting: String, value: Any) {
PostHogSDK.shared.capture(
"status_overlay_setting_changed",
properties: ["setting": setting, "value": value]
)
}
}
8 changes: 8 additions & 0 deletions PostHog.xcconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Default PostHog configuration.
// Real values are supplied via the gitignored .env.xcconfig for local builds,
// or injected before this file is read for CI/release builds. This file must
// stay tracked so xcodegen can always find a config file to load.
POSTHOG_PROJECT_TOKEN =
POSTHOG_HOST =

#include? ".env.xcconfig"
10 changes: 10 additions & 0 deletions project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ configs:
Debug: debug
Release: release

configFiles:
Debug: PostHog.xcconfig
Release: PostHog.xcconfig

packages:
PostHog:
url: https://github.com/PostHog/posthog-ios.git
from: 3.59.3

settings:
base:
CLANG_ENABLE_MODULES: "YES"
Expand All @@ -33,6 +42,7 @@ targets:
dependencies:
- sdk: CoreAudio.framework
- sdk: Carbon.framework
- package: PostHog
settings:
base:
ARCHS: arm64
Expand Down