Skip to content
Merged
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
11 changes: 7 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
## Project Overview

This is a SwiftUI-based presentation remote system with three apps:
- **Mac App** (`ClickerRemoteReceiver` v1.8): Menu bar app that receives commands and sends keystrokes to presentation software
- **iPhone App** (`ClickerRemote` v1.8): Remote control with vertical slide navigation and presentation timer
- **Apple Watch App** (`ClickerWatch` v1.8): Companion watch app with gesture-based slide control using double tap motion for next slide
- **Mac App** (`ClickerRemoteReceiver` v1.10): Menu bar app that receives commands and sends keystrokes to presentation software
- **iPhone App** (`ClickerRemote` v1.10): Remote control with vertical slide navigation and presentation timer
- **Apple Watch App** (`ClickerWatch` v1.10): Companion watch app with gesture-based slide control using double tap motion for next slide

## Tech Stack

Expand Down Expand Up @@ -64,7 +64,7 @@ The notarized DMG is created at `./build/ClickerRemoteReceiver-{version}.dmg`.
**Create GitHub release and update Homebrew tap:**
```bash
# Create the release
gh release create v1.8 ./build/ClickerRemoteReceiver-1.8.dmg --title 'Clicker v1.8' --notes 'Release notes'
gh release create v1.10 ./build/ClickerRemoteReceiver-1.10.dmg --title 'Clicker v1.10' --notes 'Release notes'

# Trigger homebrew-tap update (auto-calculates SHA256)
just update-tap
Expand Down Expand Up @@ -115,6 +115,9 @@ The `update-tap` command triggers a GitHub Action in `douinc/homebrew-tap` that:
- Bundle ID: `com.dou.clicker-ios.watchkitapp`
- Double-tap gesture via `handGestureShortcut(.primaryAction)` on watchOS 11+ (Apple Watch Series 9+ / Ultra 2) for next slide
- Note: Double-tap requires active display (wrist raised); does not work in always-on / luminance-reduced state
- Wrist flick mode uses CoreMotion gyroscope (`rotationRate.x`) to detect forward/backward wrist flicks for next/previous slide
- "No Going Back" toggle (`gestureNoGoingBack` in UserDefaults) disables backward flick gestures for forward-only navigation, reducing accidental triggers during presentations
- Flick mode settings: gesture lock (3s cooldown), invert gestures, auto-toggle with wrist raise, no going back
- Extended WatchKit runtime session (`WKExtendedRuntimeSession` with `self-care` background mode) keeps app active during presentations

## Development Team
Expand Down
2 changes: 1 addition & 1 deletion Casks/clicker-remote-receiver.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cask("clicker-remote-receiver") do
version("1.8")
version("1.10")
sha256("c93cc1b685318f5fa61d7bfb8f7e5a7896c6400e22268b7097535b5bf8ab3dc3")

url("https://github.com/douinc/clicker/releases/download/v#{version}/ClickerRemoteReceiver-#{version}.dmg")
Expand Down
2 changes: 1 addition & 1 deletion LiveActivityWidget/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.8</string>
<string>1.10</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSExtension</key>
Expand Down
2 changes: 1 addition & 1 deletion MacApp/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.8</string>
<string>1.10</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSUIElement</key>
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ Download **ClickerRemote** from the [App Store](https://apps.apple.com/us/app/cl
| **Visual Progress** | Color-coded timer bar (green → yellow → orange → red) |
| **Duration Presets** | 5, 10, 15, 20, 30 minutes or unlimited |
| **Haptic Feedback** | Vibrate every 30s, 1m, 2m, or 5m |
| **Wrist Flick Navigation** | Flick your wrist to navigate slides hands-free, with optional "No Going Back" mode for forward-only control |
| **Double-Tap Gesture** | Hands-free next slide on Apple Watch Series 9+ / Ultra 2 (watchOS 11+) |
| **Stays Active** | Extended runtime session keeps Watch app visible during presentations |

Expand Down
24 changes: 23 additions & 1 deletion WatchApp/ClickerWatchApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,48 @@ import SwiftUI
struct ClickerWatchApp: App {
@StateObject private var connectionManager = WatchConnectionManager()
@StateObject private var sessionManager = ExtendedSessionManager()
@StateObject private var gestureManager = GestureManager()
@AppStorage("gestureMode") private var gestureMode = "doubleTap"
@Environment(\.scenePhase) var scenePhase

private var isFlickMode: Bool { gestureMode == "flickWrist" }

var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(connectionManager)
.environmentObject(sessionManager)
.environmentObject(gestureManager)
.onAppear {
sessionManager.start()
wireGestureCallbacks()
}
.onChange(of: scenePhase) { _, phase in
switch phase {
case .active:
sessionManager.start()
case .inactive, .background:
if isFlickMode && gestureManager.autoToggleWithWrist {
gestureManager.start()
}
case .inactive:
if isFlickMode && gestureManager.autoToggleWithWrist {
gestureManager.stop()
}
case .background:
break
@unknown default:
break
}
}
}
}

private func wireGestureCallbacks() {
gestureManager.onNextSlide = { [weak connectionManager] in
connectionManager?.nextSlide()
}
gestureManager.onPreviousSlide = { [weak connectionManager] in
connectionManager?.previousSlide()
}
}
}
Loading
Loading