Skip to content

Add Tasker action to buzz the whoop, and ability to subscribe to double-tap from Tasker#89

Open
MrAwesome wants to merge 4 commits into
OpenStrap:mainfrom
MrAwesome:main
Open

Add Tasker action to buzz the whoop, and ability to subscribe to double-tap from Tasker#89
MrAwesome wants to merge 4 commits into
OpenStrap:mainfrom
MrAwesome:main

Conversation

@MrAwesome

@MrAwesome MrAwesome commented Jul 17, 2026

Copy link
Copy Markdown

NOTE: I have zero experience with Android development so this is heavily vibe-coded. Happy to do any testing or UI changes you see fit! Just wanted to put something up while the fire is hot.

So far, I've tested that the Tasker actions work in both directions. A double-tap can trigger profiles, and with Send Intent you can vibrate the whoop from any Tasker task.

Long-press on the action names in the menu to copy to clipboard.

As a bonus, this fixes scrolling on the double-tap menu, which previously ran off the bottom of the screen and couldn't be scrolled.

Summary by CodeRabbit

  • New Features
    • Added Tasker automation support for triggering strap buzzes and responding to double-tap gestures.
    • Added selectable buzz patterns, including testing controls in the device details view.
    • Added automation settings with easy copying of Tasker intent actions.
  • Bug Fixes
    • Improved gesture-action selection by allowing the options list to scroll on smaller screens.
  • Documentation
    • Documented the meanings of available buzz patterns.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Tasker buzz broadcasts, double-tap broadcasts, configurable BLE buzz patterns, pending startup handling, and profile controls are added across Android and Flutter. The profile also documents automation actions and supports scrolling gesture options.

Changes

Tasker integration

Layer / File(s) Summary
Buzz pattern contract
lib/ble/ble_engine.dart, guides/BUZZ_MEANINGS.md
BLE haptics accept explicit pattern values, with codes documented.
Android Tasker broadcast flow
android/app/src/main/AndroidManifest.xml, android/app/src/main/kotlin/wtf/openstrap/openstrap_edge/TaskerReceiver.kt, android/app/src/main/kotlin/wtf/openstrap/openstrap_edge/NativeChannels.kt
Android receives Tasker buzz requests, stores pending requests when Flutter is unavailable, and broadcasts double-tap events.
Flutter bridge and startup handling
lib/platform/tasker_bridge.dart, lib/state/app_state.dart
Flutter handles Tasker method calls, consumes pending requests, forwards patterns to BLE, and exposes pattern testing.
Profile automation and pattern controls
lib/gestures/device_action.dart, lib/ui/profile/profile_screen.dart, lib/ui/profile/gesture_section.dart, .gitignore
The automation action and profile controls are added, gesture options become scrollable, and FVM artifacts are ignored.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Tasker
  participant TaskerReceiver
  participant TaskerBridge
  participant AppState
  participant BleEngine
  participant NativeChannels

  Tasker->>TaskerReceiver: Send BUZZ_STRAP with pattern
  TaskerReceiver->>TaskerBridge: Invoke buzz_strap with pattern
  TaskerBridge->>AppState: Forward buzz pattern
  AppState->>BleEngine: Play haptic pattern
  AppState->>NativeChannels: Perform broadcast_to_tasker
  NativeChannels->>Tasker: Send DOUBLE_TAP broadcast
Loading

Suggested reviewers: abdulsaheel

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main Tasker integration changes: buzzing the strap and subscribing to double-tap events.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
.gitignore (1)

2-2: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep .fvmrc tracked if it is the project toolchain pin.

Ignoring .fvmrc prevents contributors and CI from receiving the same FVM configuration, allowing Flutter versions to drift. Remove this entry unless another checked-in configuration pins the intended Flutter 3.11.4 toolchain.

Proposed fix
 .fvm
-.fvmrc
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.gitignore at line 2, Remove the .fvmrc entry from .gitignore so the project
toolchain pin remains tracked; only retain the ignore rule if another checked-in
configuration explicitly pins Flutter 3.11.4.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@android/app/src/main/AndroidManifest.xml`:
- Around line 149-156: Protect the exported TaskerReceiver buzz endpoint by
documenting and enforcing a user-configured capability/token and rate limit in
the AndroidManifest.xml declaration and TaskerReceiver handler. In
TaskerReceiver, validate the capability and pattern range before persisting
data, invoking the channel, or starting the service; reject unauthorized,
invalid, or rate-limited requests while preserving Tasker accessibility.

In `@android/app/src/main/kotlin/wtf/openstrap/openstrap_edge/TaskerReceiver.kt`:
- Around line 35-42: The deferred Tasker request producer and consumer use
different preference stores. In TaskerReceiver.kt lines 35-42, align writes with
the store and key scheme consumed by consumePendingBuzz(), or implement a native
consume-and-clear bridge for openstrap_runtime; in
lib/platform/tasker_bridge.dart lines 31-38, replace the Flutter preferences
read with that matching native bridge or shared store access. Ensure deferred
requests are consumed and cleared consistently.

In `@lib/platform/tasker_bridge.dart`:
- Around line 31-38: In lib/platform/tasker_bridge.dart lines 31-38, update
consumePendingBuzz to retrieve the pending pattern without removing either
preference, and provide a separate acknowledgement/clear operation. In
lib/state/app_state.dart lines 1283-1291, defer delivery until the BLE engine is
connected, invoke engine.buzzPattern(), and acknowledge the pending request only
after the send succeeds.

In `@lib/ui/profile/profile_screen.dart`:
- Around line 353-399: Update both automation long-press handlers in the
GestureDetector widgets for “Tasker buzz strap” and “Double-tap broadcast” to be
async, await Clipboard.setData, and only call _snack with the success message
after it completes. Catch write failures and show an appropriate failure
snackbar instead.

---

Nitpick comments:
In @.gitignore:
- Line 2: Remove the .fvmrc entry from .gitignore so the project toolchain pin
remains tracked; only retain the ignore rule if another checked-in configuration
explicitly pins Flutter 3.11.4.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d1d8b418-faf0-44f4-8b98-58e4788686f5

📥 Commits

Reviewing files that changed from the base of the PR and between 8834ba0 and 2d105cd.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • .gitignore
  • android/app/src/main/AndroidManifest.xml
  • android/app/src/main/kotlin/wtf/openstrap/openstrap_edge/NativeChannels.kt
  • android/app/src/main/kotlin/wtf/openstrap/openstrap_edge/TaskerReceiver.kt
  • guides/BUZZ_MEANINGS.md
  • lib/ble/ble_engine.dart
  • lib/gestures/device_action.dart
  • lib/platform/tasker_bridge.dart
  • lib/state/app_state.dart
  • lib/ui/profile/gesture_section.dart
  • lib/ui/profile/profile_screen.dart

Comment on lines +149 to +156
<receiver
android:name=".TaskerReceiver"
android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="wtf.openstrap.openstrap_edge.BUZZ_STRAP" />
</intent-filter>
</receiver>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Protect the exported buzz endpoint.

Any installed app can explicitly target this exported receiver with the documented action, repeatedly wake the app/service, and trigger strap haptics. Keep it exported for Tasker, but require a user-configured capability/token and apply a rate limit before persisting or invoking the channel.

  • android/app/src/main/AndroidManifest.xml#L149-L156: document and enforce the chosen external-caller protection model.
  • android/app/src/main/kotlin/wtf/openstrap/openstrap_edge/TaskerReceiver.kt#L12-L50: validate the capability and pattern range before executing or starting the service.
📍 Affects 2 files
  • android/app/src/main/AndroidManifest.xml#L149-L156 (this comment)
  • android/app/src/main/kotlin/wtf/openstrap/openstrap_edge/TaskerReceiver.kt#L12-L50
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@android/app/src/main/AndroidManifest.xml` around lines 149 - 156, Protect the
exported TaskerReceiver buzz endpoint by documenting and enforcing a
user-configured capability/token and rate limit in the AndroidManifest.xml
declaration and TaskerReceiver handler. In TaskerReceiver, validate the
capability and pattern range before persisting data, invoking the channel, or
starting the service; reject unauthorized, invalid, or rate-limited requests
while preserving Tasker accessibility.

Comment on lines +35 to +42
val prefs = context.getSharedPreferences(
"openstrap_runtime",
Context.MODE_PRIVATE
)
prefs.edit()
.putBoolean(PENDING_BUZZ_KEY, true)
.putInt(PENDING_PATTERN_KEY, pattern)
.apply()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For shared_preferences version 2.3.3 on Android, does SharedPreferences.getInstance() read an arbitrary native preference file named "openstrap_runtime", or only the plugin-managed default store? Include the key-prefix behavior.

💡 Result:

For the shared_preferences plugin (version 2.3.3), the SharedPreferences.getInstance method interacts with a default, plugin-managed SharedPreferences file on Android, not an arbitrary file named "openstrap_runtime" [1][2][3]. By default, the plugin uses a specific SharedPreferences file internally [4]. It does not automatically target or read from other arbitrary native preference files like "openstrap_runtime". Regarding key-prefix behavior: 1. Default Prefix: By default, the plugin only reads and writes preferences that begin with the prefix "flutter." [5][3]. This filtering is handled automatically by the plugin [5][3]. 2. Custom Prefixes: You can change this behavior by calling setPrefix before instantiating any SharedPreferences object [5][3]. If you set the prefix to an empty string (""), the plugin will read all keys within that SharedPreferences file, which is often used for migrating existing native Android preferences to Flutter [5][3]. 3. Interaction with Async APIs: While newer APIs like SharedPreferencesAsync (available in later versions) allow specifying custom file names via SharedPreferencesAsyncAndroidOptions (e.g., using AndroidSharedPreferencesStoreOptions(fileName: '...')), the standard SharedPreferences.getInstance API in version 2.3.3 remains tied to the plugin's default storage configuration [6][1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant files first
ast-grep outline android/app/src/main/kotlin/wtf/openstrap/openstrap_edge/TaskerReceiver.kt --view expanded || true
ast-grep outline lib/platform/tasker_bridge.dart --view expanded || true

# Read the relevant slices with line numbers
echo '--- TaskerReceiver.kt ---'
cat -n android/app/src/main/kotlin/wtf/openstrap/openstrap_edge/TaskerReceiver.kt | sed -n '1,220p'

echo '--- tasker_bridge.dart ---'
cat -n lib/platform/tasker_bridge.dart | sed -n '1,220p'

Repository: OpenStrap/edge

Length of output: 4691


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate any shared_preferences usage and any native bridge for pending buzz
rg -n "SharedPreferences\.getInstance|openstrap_runtime|PENDING_BUZZ_KEY|PENDING_PATTERN_KEY|consumePendingBuzz|setPrefix|flutter\." android lib

Repository: OpenStrap/edge

Length of output: 8442


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find where shared_preferences plugin behavior is configured, if at all
rg -n "SharedPreferences\.setPrefix|SharedPreferencesAsync|AndroidSharedPreferencesStoreOptions|SharedPreferencesWithCache|shared_preferences" lib android

Repository: OpenStrap/edge

Length of output: 2622


Use the same store for deferred Tasker requests. SharedPreferences.getInstance() reads the plugin-managed FlutterSharedPreferences store (flutter. keys), so the openstrap_runtime entries written here are invisible to consumePendingBuzz().

  • android/app/src/main/kotlin/wtf/openstrap/openstrap_edge/TaskerReceiver.kt#L35-L42: write to the same store/key scheme, or expose a native consume-and-clear bridge for openstrap_runtime.
  • lib/platform/tasker_bridge.dart#L31-L38: call that bridge instead of the Flutter preferences plugin.
📍 Affects 2 files
  • android/app/src/main/kotlin/wtf/openstrap/openstrap_edge/TaskerReceiver.kt#L35-L42 (this comment)
  • lib/platform/tasker_bridge.dart#L31-L38
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@android/app/src/main/kotlin/wtf/openstrap/openstrap_edge/TaskerReceiver.kt`
around lines 35 - 42, The deferred Tasker request producer and consumer use
different preference stores. In TaskerReceiver.kt lines 35-42, align writes with
the store and key scheme consumed by consumePendingBuzz(), or implement a native
consume-and-clear bridge for openstrap_runtime; in
lib/platform/tasker_bridge.dart lines 31-38, replace the Flutter preferences
read with that matching native bridge or shared store access. Ensure deferred
requests are consumed and cleared consistently.

Comment on lines +31 to +38
static Future<int?> consumePendingBuzz() async {
final prefs = await SharedPreferences.getInstance();
final pending = prefs.getBool(_pendingKey) ?? false;
if (!pending) return null;
final pattern = prefs.getInt(_pendingPatternKey) ?? 2;
await prefs.remove(_pendingKey);
await prefs.remove(_pendingPatternKey);
return pattern;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not acknowledge a pending buzz before BLE is ready.

_init() starts openSession() without awaiting it, then immediately consumes and deletes the pending request. engine.buzzPattern() can therefore run before a connection exists, where its write is skipped and the request is lost.

  • lib/platform/tasker_bridge.dart#L31-L38: split read from acknowledgement; do not clear the request during retrieval.
  • lib/state/app_state.dart#L1283-L1291: deliver only after the engine is connected, then acknowledge/clear after a successful send.
📍 Affects 2 files
  • lib/platform/tasker_bridge.dart#L31-L38 (this comment)
  • lib/state/app_state.dart#L1283-L1291
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/platform/tasker_bridge.dart` around lines 31 - 38, In
lib/platform/tasker_bridge.dart lines 31-38, update consumePendingBuzz to
retrieve the pending pattern without removing either preference, and provide a
separate acknowledgement/clear operation. In lib/state/app_state.dart lines
1283-1291, defer delivery until the BLE engine is connected, invoke
engine.buzzPattern(), and acknowledge the pending request only after the send
succeeds.

Comment on lines +353 to +399
// ── Automation (Tasker) ──────────────────────────────────────
const SectionHeader('Automation'),
_SettingsCard(rows: [
GestureDetector(
onLongPress: () {
Clipboard.setData(
const ClipboardData(text: 'wtf.openstrap.openstrap_edge.BUZZ_STRAP'),
);
_snack(context, 'Copied BUZZ_STRAP action.');
},
child: ListRow(
icon: OsIcon.activity,
title: 'Tasker buzz strap',
subtitle: 'wtf.openstrap.openstrap_edge.BUZZ_STRAP',
onTap: null,
),
),
]),
const _CardNote(
'Send a Broadcast intent from Tasker or any automation app to '
'vibrate your WHOOP strap. On Android 12+, set Package to '
'wtf.openstrap.openstrap_edge. Optional int extra "pattern" (default 2).',
),
const SizedBox(height: Sp.x3),
_SettingsCard(rows: [
GestureDetector(
onLongPress: () {
Clipboard.setData(
const ClipboardData(text: 'wtf.openstrap.openstrap_edge.DOUBLE_TAP'),
);
_snack(context, 'Copied DOUBLE_TAP action.');
},
child: ListRow(
icon: OsIcon.activity,
title: 'Double-tap broadcast',
subtitle: 'wtf.openstrap.openstrap_edge.DOUBLE_TAP',
onTap: null,
),
),
]),
const _CardNote(
'Set double-tap gesture to "Broadcast to Tasker", then create '
'a Tasker profile: Event → Intent Received → action above.',
),

const SizedBox(height: Sp.x6),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file around the reported lines
nl -ba lib/ui/profile/profile_screen.dart | sed -n '320,430p'

# Find the helper and any existing async patterns in the file
rg -n "Clipboard\.setData|_snack\(|mounted|onLongPress:" lib/ui/profile/profile_screen.dart

Repository: OpenStrap/edge

Length of output: 192


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the target section with line numbers using tools more likely to be available
sed -n '320,430p' lib/ui/profile/profile_screen.dart | cat -n

# Inspect clipboard usage and mounted checks elsewhere in the file
rg -n "Clipboard\.setData|_snack\(|mounted|onLongPress:" lib/ui/profile/profile_screen.dart

Repository: OpenStrap/edge

Length of output: 6863


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether the file or project already uses async clipboard handling patterns
rg -n "await Clipboard\.setData|try \{|catch \(|context\.mounted|if \(mounted\)" lib/ui/profile/profile_screen.dart lib/ui -g '*.dart'

Repository: OpenStrap/edge

Length of output: 18290


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the reported region with line numbers
sed -n '340,405p' lib/ui/profile/profile_screen.dart | cat -n

# Locate exact clipboard calls and any async error handling nearby
rg -n "Clipboard\.setData|_snack\(|context\.mounted|mounted|onLongPress:" lib/ui/profile/profile_screen.dart

Repository: OpenStrap/edge

Length of output: 4719


Await the clipboard write before showing success.
Clipboard.setData is async here, so both long-press handlers can show a success snackbar before the copy completes. Make them async, await the call, and show a failure message if the write throws.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/ui/profile/profile_screen.dart` around lines 353 - 399, Update both
automation long-press handlers in the GestureDetector widgets for “Tasker buzz
strap” and “Double-tap broadcast” to be async, await Clipboard.setData, and only
call _snack with the success message after it completes. Catch write failures
and show an appropriate failure snackbar instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant