-
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 |