-
Notifications
You must be signed in to change notification settings - Fork 0
API Reference
This reference documents the key classes and protocols in the codebase.
The command protocol between iPhone and Mac:
// Shared/RemoteCommand.swift
enum RemoteCommand: String, Codable {
case nextSlide = "next"
case previousSlide = "previous"
case startPresentation = "start"
case endPresentation = "end"
case blackScreen = "black"
case keepalive = "keepalive"
var keyCode: UInt16? { ... }
}Wire Format: JSON encoded as {"rawValue": "next"}
Handles MultipeerConnectivity on the Mac side.
@MainActor
class MacConnectionManager: NSObject, ObservableObject {
@Published var isConnected: Bool
@Published var connectedDeviceName: String?
func startAdvertising()
func stopAdvertising()
}| Property | Type | Description |
|---|---|---|
isConnected |
Bool |
True when iPhone is connected |
connectedDeviceName |
String? |
Name of connected iPhone |
Protocols Implemented:
MCNearbyServiceAdvertiserDelegateMCSessionDelegate
Injects keyboard events into the system.
class KeystrokeSender {
static let shared: KeystrokeSender
func sendNext() // Sends Right Arrow (key code 124)
func sendPrevious() // Sends Left Arrow (key code 123)
func sendStart() // Sends Return (key code 36)
func sendEnd() // Sends Escape (key code 53)
func sendBlackScreen() // Sends B key (key code 11)
}Requirements: Accessibility permission
| Action | Key Code | Key |
|---|---|---|
| Next Slide | 124 | → Right Arrow |
| Previous Slide | 123 | ← Left Arrow |
| Start Presentation | 36 | Return |
| End Presentation | 53 | Escape |
| Black Screen | 11 | B key |
Handles MultipeerConnectivity on the iPhone side.
@MainActor
class iPhoneConnectionManager: NSObject, ObservableObject {
@Published var isConnected: Bool
@Published var availablePeers: [MCPeerID]
@Published var connectedPeerName: String?
func startBrowsing()
func stopBrowsing()
func connect(to peer: MCPeerID)
func disconnect()
func sendCommand(_ command: RemoteCommand)
}| Property | Type | Description |
|---|---|---|
isConnected |
Bool |
True when connected to Mac |
availablePeers |
[MCPeerID] |
Discovered Mac devices |
connectedPeerName |
String? |
Name of connected Mac |
Protocols Implemented:
MCNearbyServiceBrowserDelegateMCSessionDelegate
Manages the presentation timer with haptic feedback.
@MainActor
class PresentationTimer: ObservableObject {
@Published var isRunning: Bool
@Published var elapsedTime: TimeInterval
@Published var duration: TimeInterval
@Published var hapticInterval: TimeInterval
var progress: Double { elapsedTime / duration }
func start()
func pause()
func reset()
}| Property | Type | Description |
|---|---|---|
isRunning |
Bool |
Timer active state |
elapsedTime |
TimeInterval |
Seconds elapsed |
duration |
TimeInterval |
Total duration (0 = unlimited) |
hapticInterval |
TimeInterval |
Seconds between haptic alerts |
progress |
Double |
0.0 to 1.0 progress value |
Duration Presets: 5, 10, 15, 20, 30 minutes, unlimited
Haptic Intervals: 30s, 1m, 2m, 5m
Handles StoreKit 2 subscription logic.
@Observable
class SubscriptionManager {
var status: SubscriptionStatus
func checkEntitlements() async
func purchase(_ product: Product) async throws
func restorePurchases() async
}enum SubscriptionStatus {
case notDetermined
case trial(daysRemaining: Int)
case subscribed(expiresAt: Date?)
case expired
}Manages the 7-day trial period via Keychain.
class TrialTracker {
var hasStartedTrial: Bool
var trialStartDate: Date?
var daysRemaining: Int
var isTrialExpired: Bool
func startTrial()
}Storage: Keychain (survives app reinstall)
| View | Description |
|---|---|
ContentView |
Main menu bar content |
ConnectionStatusView |
Shows connection state |
| View | Description |
|---|---|
SubscriptionGateView |
Entry point, checks subscription |
ContentView |
Main container |
ConnectionView |
Device discovery and connection |
RemoteControlView |
Slide navigation buttons |
TimerView |
Presentation timer |
TimerSettingsView |
Timer configuration sheet |
PaywallView |
Subscription purchase UI |
Handles WatchConnectivity communication with the iPhone.
class WatchConnectionManager: NSObject, ObservableObject {
@Published var isReachable: Bool
@Published var isConnectedToMac: Bool
func sendCommand(_ command: String)
func nextSlide()
func previousSlide()
}| Property | Type | Description |
|---|---|---|
isReachable |
Bool |
True when iPhone is reachable |
isConnectedToMac |
Bool |
True when iPhone is connected to Mac |
Protocols Implemented:
WCSessionDelegate
Retry Logic: Commands are retried up to 3 times at 0.5s intervals if the iPhone is temporarily unreachable.
Detects wrist flick gestures using CoreMotion for hands-free slide control.
class GestureManager: ObservableObject {
@Published var isEnabled: Bool
@Published var lastGesture: DetectedGesture?
@Published var isInverted: Bool
@Published var autoToggleWithWrist: Bool
@Published var gestureLockEnabled: Bool
@Published var noGoingBack: Bool
@Published var isLocked: Bool
@Published var lockProgress: CGFloat
func start()
func stop()
func toggle()
}| Property | Type | Description |
|---|---|---|
isEnabled |
Bool |
Whether gesture detection is active |
lastGesture |
DetectedGesture? |
Most recently detected gesture (.next or .previous) |
isInverted |
Bool |
Swap forward/backward flick directions |
autoToggleWithWrist |
Bool |
Auto-enable on wrist raise, disable on wrist lower |
gestureLockEnabled |
Bool |
Enable 3-second cooldown after each gesture |
noGoingBack |
Bool |
Ignore backward flick gestures (forward-only navigation) |
isLocked |
Bool |
Currently in lock cooldown period |
lockProgress |
CGFloat |
Lock countdown progress (1.0 → 0.0) |
| View | Description |
|---|---|
ContentView |
Next/previous buttons + timer display + double-tap gesture support; in flick mode, previous button is disabled when "No Going Back" is on |
SettingsView |
Watch app settings (gesture mode, gesture lock, no going back, invert gestures, auto-toggle, invert crown) |
Timer Gestures:
- Tap: Start/stop timer
- Long press: Reset timer to 00:00 with haptic feedback
| App | Bundle ID |
|---|---|
| ClickerRemoteReceiver (Mac) | com.dou.clicker-mac |
| ClickerRemote (iOS) | com.dou.clicker-ios |
| ClickerWatch (watchOS) | com.dou.clicker-ios.watchkitapp |
| Product | ID |
|---|---|
| Annual Subscription | com.dou.clicker.annual |