Skip to content

Add WearOS companion app#3185

Merged
TheLastProject merged 50 commits into
CatimaLoyalty:wip/wearosfrom
blacklight:feat/wearos
Jul 19, 2026
Merged

Add WearOS companion app#3185
TheLastProject merged 50 commits into
CatimaLoyalty:wip/wearosfrom
blacklight:feat/wearos

Conversation

@blacklight

@blacklight blacklight commented Jul 2, 2026

Copy link
Copy Markdown

Add WearOS companion app

Adds a standalone Wear OS app that displays a user's loyalty cards and renders their barcodes directly on the watch.

Addresses:

Closes:

What's new

Wear OS module (wear/)

  • Card list screen showing all non-archived loyalty cards fetched from the phone, with per-card header colours
  • Card detail screen rendering the barcode (QR, EAN-13, Code 128, Aztec, Data Matrix, etc.) using ZXing, constrained to fit within a round watch face
  • Swipe-to-dismiss navigation between screens
  • BluetoothCardClient — GMS-free RFCOMM client that connects to any bonded device advertising the Catima service UUID and fetches card JSON over a newline-delimited protocol
  • WearDataListenerService — GMS WearableListenerService retained as automatic fallback for standard Android + Google Play Services setups
  • Graceful error state when neither transport can reach the phone

Phone module (app/)

  • BluetoothServerService — RFCOMM server that starts on app launch, listens for CARDS_REQUEST commands from the watch and responds with card list JSON
  • WearDataService — GMS WearableListenerService retained as fallback
  • BLUETOOTH_CONNECT runtime permission requested on first launch (Android 12+)

Transport strategy

The watch tries Bluetooth Classic (RFCOMM) first. This works without Google Play Services and is therefore compatible with GrapheneOS and other setups where sandboxed GMS cannot route Wearable Data Layer messages. If no bonded device responds on the Catima UUID, it falls back to the GMS MessageClient path, which works on standard Android.

Key fixes

  • org.json.JSONObject.optString() returns the literal string "null" for JSON null values; replaced with isNull() guards in WearCard.fromJson to prevent ZXing receiving "null" as a barcode value
  • Square barcodes (QR, Aztec, Data Matrix) are now constrained with a 15 % round-screen inset via BoxWithConstraints so they do not overflow the circular display boundary

Permissions added

  • BLUETOOTH / BLUETOOTH_ADMIN (≤ API 30, no prompt required)
  • BLUETOOTH_CONNECT (API 31+, runtime prompt on both phone and watch)

Testing

Verified on Pixel Watch 3 (Wear OS 5) paired with Pixel 9a running GrapheneOS with the F-Droid build of Catima. Full round-trip latency phone → watch is approximately 400 ms over Bluetooth Classic.

Screenshots

watch_media_2026-07-02_17_47_04 watch_media_2026-07-02_17_47_19

- BluetoothServerService: RFCOMM server on phone, listens for CARDS_REQUEST
  and responds with card list JSON over a newline-delimited protocol
- BluetoothCardClient: watch-side client, iterates bonded devices trying the
  Catima UUID, falls back to GMS Wearable Data Layer if BT fails
- Runtime BLUETOOTH_CONNECT permission requests on both phone and watch
- standalone=true + wearable lib optional on watch (no GMS dependency)
- Bluetooth-first with automatic GMS fallback ensures compatibility with
  both GrapheneOS/FOSS and standard Android+GMS setups
- Fix barcode not shown for cards with null barcodeId: org.json.optString()
  returns literal string 'null' for JSON null values; use isNull() check first
- Fix QR/Aztec/DataMatrix overflow on round watch: use BoxWithConstraints to
  measure screen size and apply a 15% round-screen inset to constrain the
  square barcode to stay within the circular display boundary
@TheLastProject

Copy link
Copy Markdown
Member

As written in the issues, WearOS requires proprietary dependencies. So this must be at very least be excluded from the foss flavour. That is a blocker, because F-Droid does not allow proprietary dependencies (and I don't want people to be forced to run proprietary Google code to run Catima, there must always be an option free is proprietary code)

Aside from that, cool :)

I don't have a WearOS device but I will look at this in an emulator soon. Are you willing to keep maintaining WearOS code long term? Just for my expectation of expected (future) workload :)

After a reboot the BT fetch completes while the watch screen is still
off/settling. The StateFlow update is lost because Compose hasn't
composed yet in that window. Fix by:
- Moving card fetch to onResume so it runs every time the user wakes
  the screen, not just once in onCreate
- Adding fetchInFlight guard to prevent concurrent duplicate requests
- Calling WearCardRepository.reset() at the start of each fetch to
  clear stale state and re-show the spinner during refresh
Android's app standby mechanism stops background services when the app
is idle (~5 min). Promote BluetoothServerService to a foreground service
with a silent low-priority notification so the OS cannot kill it.

- Add FOREGROUND_SERVICE + FOREGROUND_SERVICE_CONNECTED_DEVICE permissions
- Add foregroundServiceType="connectedDevice" to manifest declaration
- Call startForeground() on service start with a silent IMPORTANCE_MIN
  notification (uses FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE on API 34+)
- Restart the service via stopSelf() if the accept loop exits unexpectedly,
  relying on START_STICKY to have the OS bring it back up
@blacklight

blacklight commented Jul 2, 2026

Copy link
Copy Markdown
Author

@TheLastProject thank you for your reply!

I had the same uncertainty around the non-free WearOS dependencies.

I eventually resorted to implementing my own Bluetooth service to let the phone and watch communicate (also because GMS didn't work on my sandboxed Google Play Services on GrapheneOS), but there's still a GMS fallback that makes the app dependent on Google's non-free libraries, I agree.

I'm not very familiar with F-Droid's requirements when it comes to WearOS apps and I don't know if there are some relevant examples I could take a look at.

But my guess is that without the GMS dependency and with a custom Bluetooth listener to replace it the lights should be green, right?

If that's still not the case I'll be happy to split the WearOS implementation and make it only available for the Play Store build.

Are you willing to keep maintaining WearOS code long term?

Of course 🙂

- Add KeepScreenOnAtMaxBrightness() composable using DisposableEffect:
  sets FLAG_KEEP_SCREEN_ON and BRIGHTNESS_OVERRIDE_FULL on the window
  when CardDetail is composed, restores original flags/brightness on dispose
- Switch CardDetail to white background with black text/barcode so codes
  are displayed as dark-on-light (required for scanner compatibility and
  better contrast under store lighting)
@TheLastProject

Copy link
Copy Markdown
Member

I eventually resorted to implementing my own Bluetooth service to let the phone and watch communicate (also because GMS didn't work on my sandboxed Google Play Services on GrapheneOS), but there's still a GMS fallback that makes the app dependent on Google's non-free libraries, I agree.

I'm not very familiar with F-Droid's requirements when it comes to WearOS apps and I don't know if there are some relevant examples I could take a look at.

But my guess is that without the GMS dependency and with a custom Bluetooth listener to replace it the lights should be green, right?

Yeah, the issue is specifically

    // Wearable Data Layer
    implementation(libs.com.google.android.gms.play.services.wearable)

in app/build.gradle.kts. That has to be excluded from the foss flavour.

I think that can be solved by just saying gPlayImplementation instead of implementation to only include it in the gPlay flavour.

With regards to testing, I see you removed the .debug suffix from the debug builds. This will make it very hard for me to do an actual on-device test. Maybe it's possible to add a debug version to the Wear app as well with debug in the package name? I believe there is this annoying Google restricting where the wear app must almost share a package name or so, which I guess is why you did that.

Are you willing to keep maintaining WearOS code long term?

Of course 🙂

That's great :)

I see you're still pushing to this occasionally. Would you like me to start reviewing already or give you some times to finish up some stuff first? :)

The Bluetooth RFCOMM transport is now the sole data channel, making
GMS a hard dependency with no benefit. Removed entirely:

- WearDataListenerService (wear) - GMS WearableListenerService
- WearDataService (phone) - GMS WearableListenerService
- requestCardsViaGms() in wear MainActivity
- GMS path/key constants from WearProtocol (only BT constants remain)
- WearDataListenerService + WearDataService from both manifests
- com.google.android.wearable uses-library + standalone meta-data from wear manifest
- play-services-wearable from wear/build.gradle.kts
- play-services-wearable from app/build.gradle.kts
- com-google-android-gms-play-services-wearable from libs.versions.toml

BT failure now goes directly to setPhoneNotReachable() showing the
'phone not connected' error state.
Cards are now stored in SharedPreferences after each successful BT sync.
On launch, cached cards are shown immediately before the BT request
completes, so the UI is never blank if the phone isn't available.

- WearCardStore: new singleton that reads/writes card list JSON to
  SharedPreferences (catima_wear_cards / cards_json)
- WearCardRepository: loadCache() pre-populates _cards from store on
  app start; updateCards() now persists to store after each sync;
  new syncing StateFlow; setPhoneNotReachable() always sets the flag
  so the UI can indicate offline state even when cards are shown
- MainActivity: calls loadCache() before setContent so cached cards
  are available for the first composition; setSyncing(true) replaces
  reset() so existing cards remain visible during refresh; passes
  Context to updateCards()
- CardListScreen: new syncing param; shows 'Syncing with phone…'
  footer while BT request is in flight; shows 'Offline – showing
  cached cards' footer when phone was unreachable but cards exist;
  full 'phone not connected' screen only shown when cards=null+offline
@TheLastProject

Copy link
Copy Markdown
Member

I just merged the Katastima APK scanner into main. That should make it clear which dependencies are in which flavour if you rebase your MR on main :)

- Added again to the `app` build.gradle.kts
- Added also to the `wear` build
@blacklight

blacklight commented Jul 2, 2026

Copy link
Copy Markdown
Author

I just pushed two last changes:

  • 744a7453 to remove all the GMS dependencies (no more GMS fallback, only Bluetooth RFCOMM service).
  • aa72cc19 adds watch storage support, so cached cards will still be displayed also if the phone is disconnected.

The code should be ready for review now.

One last open question would be about the packaging/release process.

I don't know of other WearOS apps distributed on F-Droid so I'm not sure how the distribution process for companion apps would look like.

On the Play Store the APKs are usually bundled, not sure about F-Droid - if they can be released together or the WearOS app needs another store entry.

Also, I guess that a mention in the README/docs is also needed?

@TheLastProject

Copy link
Copy Markdown
Member

The code should be ready for review now.

Great, I'll look soon. Though the lint seems to be failing. We should also see if we can do the whole build/test of the WearOS app in GitHub Actions. But that's not something to delay review (what delays review is me not having time yet but I'll make time soon!)

One last open question would be about the packaging/release process.

I don't know of other WearOS apps distributed on F-Droid so I'm not sure how the distribution process for companion apps would look like.

On the Play Store the APKs are usually bundled, not sure about F-Droid - if they can be released together or the WearOS app needs another store entry.

I don't believe it is possible to write WearOS apps without proprietary dependencies, so that's a no-go for F-Droid. However, I can post it to GitHub and maybe it can be published to IzzyOnDroid. I don't really know enough yet about installing apps on WearOS, trying to hunt down a cheap second-hand WearOS watch and then discuss with Izzy afterwards :)

Also, I guess that a mention in the README/docs is also needed?

I guess so, but no rush, we can also set the documentation up after this is merged :)

@TheLastProject

Copy link
Copy Markdown
Member

Not completely sure but I think this may be what we want as a new wear.yml GitHub action (based on android.yml), so we can automatically build and lint the wear app too each commit :)

I removed all the test stuff, as the wear app seems to lack tests (which is understandable, I'm not going to complain).

name: Android Wear CI
on:
  workflow_dispatch:
  push:
    branches:
      - main
      - staging
      - trying
  pull_request:
    branches:
      - main
permissions:
  actions: none
  checks: none
  contents: read
  deployments: none
  discussions: none
  id-token: none
  issues: none
  packages: none
  pages: none
  pull-requests: none
  repository-projects: none
  security-events: none
  statuses: none
env:
  JAVA_HOME: /usr/lib/jvm/java-25-openjdk-amd64
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7.0.0
      - uses: gradle/actions/wrapper-validation@v6.2.0
      - name: set up OpenJDK 25
        run: |
          sudo apt-get update
          sudo apt-get install -y openjdk-25-jdk-headless
          sudo update-alternatives --auto java
      - name: Build
        run: ./gradlew :wear:assembleRelease
      - name: Check for non-free libraries
        run: |
          wget -q https://codeberg.org/Katastima/apkscanner/releases/download/v0.0.7/apk-scanner_v0.0.7.jar
          java -jar apk-scanner_v0.0.7.jar scan-apk "$(echo 'wear/build/outputs/apk/release/wear-release-unsigned.apk')"
      - name: Check lint
        run: ./gradlew :wear:lintRelease

@TheLastProject

Copy link
Copy Markdown
Member

Sadly my attempts to test this have been foiled by the Google Pixel Watch app, which is required to link a WearOS app according to Android Studio, crashing on real devices and emulators for me.

image

Maybe the link-to-emulated-watch flow is just broken. I'll try to get my hands on a second hand Android WearOS watch soon and see if that behaves better.

I'll take a look at the code itself soon :)

@TheLastProject TheLastProject left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks quite reasonable! I feel I don't have much complaints about the general structure, great.

I haven't been able to test it yet, but I'm in talks with someone to get a second hand WearOS device. I may run into some more issues when testing on device, but for now, this is my feedback (also don't forget about the suggested GitHub Actions workflow please :))

Comment thread app/src/main/java/protect/card_locker/wearos/BluetoothServerService.kt Outdated
Comment thread app/src/main/java/protect/card_locker/BluetoothServerService.kt Outdated
Comment thread app/src/main/java/protect/card_locker/BluetoothServerService.kt Outdated
Comment thread app/src/main/java/protect/card_locker/BluetoothServerService.kt Outdated
Comment thread app/src/main/java/protect/card_locker/wearos/BluetoothServerService.kt Outdated
Comment thread wear/src/main/java/me/hackerchick/catima/wear/WearCardRepository.kt
Comment thread wear/src/main/java/me/hackerchick/catima/wear/WearCardStore.kt Outdated
Comment thread wear/src/main/java/me/hackerchick/catima/wear/WearProtocol.kt Outdated
Comment thread wear/build.gradle.kts
Comment thread .gitignore
Add PROTOCOL_VERSION (1) to the phone↔watch Bluetooth protocol so
both sides can detect an outdated companion app.

- Phone: accept V1/CARDS_REQUEST and return {"version":1,"cards":[...]}
- Phone: keep plain-array response for legacy CARDS_REQUEST
- Wear: send V1/CARDS_REQUEST and parse the version envelope
- Wear: display "phone outdated" / "watch outdated" messages

Closes: CatimaLoyalty#3185 (comment)
Add a "Sync with Wear OS" toggle in settings and only start the
Bluetooth companion server when the setting is enabled and the
BLUETOOTH_CONNECT permission is granted.

- Add Wear OS preference category and toggle
- Gate service start in LoyaltyCardLockerApplication
- Remove redundant direct startService in MainActivity.onCreate
- Keep permission-callback start for first-time grant

Addresses: CatimaLoyalty#3185 (comment)
@TheLastProject

Copy link
Copy Markdown
Member

I expect to have a WearOS smartwatch by tuesday evening so I may be able to test this on-device Tuesday evening or Wednesday, assuming the official WearOS app actually works properly. So I can return to this with more on-device experience then.

…ings

- Default pref_wear_sync to false (opt-in rather than opt-out)
- Remove BT permission request from MainActivity; it now lives exclusively
  in SettingsFragment where the toggle is visible and context is clear
- Request BLUETOOTH_CONNECT immediately when the toggle is turned on
- Start/stop BluetoothServerService reactively on toggle change
- Re-request permission on SettingsFragment resume to handle cases where
  Android revoked it while the setting was still enabled
- Simplify processParseResultList() by removing unused group/closeApp params

Addresses: CatimaLoyalty#3185 (comment)
…Screen

- Replace unsafe `LocalContext.current as? Activity` with `LocalActivity.current`
  in `KeepScreenOnAtMaxBrightness`.
- Refactor `generateBarcode` to return a `BarcodeResult` carrying both
  the bitmap and `isSquare` flag, removing the redundant `BarcodeFormat`
  lookup in the composable.

Addresses: CatimaLoyalty#3185 (comment)
@blacklight
blacklight requested a review from theimpulson July 14, 2026 09:25

@TheLastProject TheLastProject left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this may almost be ready to be merged at least into a wearos branch. I'm a bit hesitant to release this as-is because I'm not sure if the assumption that any paired Bluetooth device can be trusted is one that holds true.

However, I think that it may be good to do some security hardening in a separate MR after this base v1 MR is merged, to prevent scope creeping too much.

I think this is basically ready aside from a small refactor.

I do seem to have performance issues, but I do think they may just be issues with the watch itself? I'm not seeing any obvious big mistakes. And it's usable enough, so whatever :)

Comment thread wear/src/main/AndroidManifest.xml
Comment thread wear/build.gradle.kts Outdated
Comment thread app/src/main/java/protect/card_locker/wearos/BluetoothServerService.kt Outdated
Comment thread app/src/main/java/protect/card_locker/LoyaltyCardLockerApplication.java Outdated
Comment thread gradle/libs.versions.toml Outdated
Comment thread wear/src/main/java/me/hackerchick/catima/wear/BluetoothCardClient.kt Outdated
Comment thread wear/src/main/java/me/hackerchick/catima/wear/BluetoothCardClient.kt Outdated
Comment thread wear/src/main/java/me/hackerchick/catima/wear/SyncStatus.kt
Comment thread app/src/main/java/protect/card_locker/preferences/SettingsActivity.kt Outdated
blacklight and others added 5 commits July 15, 2026 01:32
Co-authored-by: Sylvia van Os <sylvia@hackerchick.me>
Use distinct `applicationId` for wear

Co-authored-by: Sylvia van Os <sylvia@hackerchick.me>
- Add /VERSIONS endpoint and versioned /V1/ command prefix
- Drop version field from page responses and validate via negotiation on watch
- Stop repeated fetch attempts when protocol is incompatible

Resolves: CatimaLoyalty#3185 (comment)
Always start the Wear OS Bluetooth server from the Application when Wear
sync is on; the service already stops itself if BLUETOOTH_CONNECT is
missing. Request the permission again from MainActivity.onResume so that
Android's auto-revocation of unused app permissions doesn't leave Wear
sync silently broken.
Fixed ending newline

Co-authored-by: Sylvia van Os <sylvia@hackerchick.me>
blacklight added a commit to blacklight/Catima that referenced this pull request Jul 15, 2026
Add SYNC_ERROR fetch status for unparseable phone responses

Resolves: CatimaLoyalty#3185 (comment)
Add SYNC_ERROR fetch status for unparseable phone responses

Resolves: CatimaLoyalty#3185 (comment)

@TheLastProject TheLastProject left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

FYI, you have a merge conflict on gradle/libs.versions.toml now, probably due to the new release, so that needs fixing.

Also still have some nitpicks, sorry 😅

Comment thread app/src/main/java/protect/card_locker/LoyaltyCardLockerApplication.java Outdated
Comment thread wear/build.gradle.kts
}

buildTypes {
release {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I agree it's a good improvement, especially because Google notes the old system is deprecated. I made a new issue for that, we can do that for app + wear after this I think: #3208

Comment thread wear/build.gradle.kts

android {
namespace = "me.hackerchick.catima.wear"
compileSdk = 36

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I agree it would be an improvement as soon as we can use targetSdk 37.

I configured it in https://github.com/CatimaLoyalty/Android/pull/3161/changes, will also bump the wear app after it's been merged. Let me know in that PR if you think I'm making a mistake. For now, the tests fails because Robolectric doesn't support SDK 37 yet.


setContent {
CatimaWearTheme {
val navController = rememberSwipeDismissableNavController()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I trust @theimpulson's judgment in that we should use navigation3. However, I'm okay with leaving that for a future PR as well so we can move forward with getting this merged and getting something usable into the hands of users :)

Comment thread app/src/main/java/protect/card_locker/preferences/SettingsActivity.kt Outdated
Comment thread app/src/main/java/protect/card_locker/preferences/SettingsActivity.kt Outdated
Comment thread wear/src/main/java/me/hackerchick/catima/wear/SyncStatus.kt
@TheLastProject

Copy link
Copy Markdown
Member

Sorry if this review is a bit chaotic, some of your comments came in while I was responding and I missed them. I think I looked at everything and replied to everything but if I missed something please let me know

* main: (24 commits)
  Bump com.android.application from 9.2.1 to 9.3.0
  Bump Fastlane version
  Release Catima 2.43.0
  Update Fastlane changelogs
  Update CHANGELOG
  Add internal image viewer for zooming (CatimaLoyalty#3184)
  Bump androidx.test.uiautomator:uiautomator from 2.3.0 to 2.4.0
  Bump androidx.compose:compose-bom from 2026.06.00 to 2026.06.01
  Bump ReactiveCircus/android-emulator-runner from 2 to 2.37.0
  Update CONTRIBUTING.md
  docs: highlight llm usage note
  docs: note on migration for contributors
  Bump gradle-wrapper from 9.6.0 to 9.6.1
  Update Fastlane changelogs
  Update CHANGELOG
  Merge pull request CatimaLoyalty#3191 from Bhargav-24/add-most-recently-added-sort
  Update Fastlane changelogs
  Update CHANGELOG
  Merge pull request CatimaLoyalty#3174 from mvanhorn/feat/2742-display-card-id
  Translated using Weblate (Persian)
  ...
Extract Wear OS sync permission handling and Bluetooth server service
start/stop logic into a shared manager.

Update the main and settings activities to use it, ensuring the service
is stopped when sync is disabled or permission is denied/revoked.
Replace the local `BluetoothCardClient.FetchStatus` enum with the shared
`SyncStatus` enum so the wear module uses one status type across
`BluetoothCardClient`, `MainActivity`, and `WearCardRepository`.

Addresses: CatimaLoyalty#3185 (comment)
Comment thread gradle/libs.versions.toml
Comment thread wear/src/main/AndroidManifest.xml Outdated
Comment on lines +171 to +174
wearSyncPreference!!.setOnPreferenceChangeListener { _, newValue ->
wearSyncPermissionRequester.onWearSyncChanged(newValue as Boolean)
true
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When activating "Sync with Wear OS", when the user denies the permission request, the toggle will still be activated. It shouldn't be.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Even worse, that seems to have broken the main screen and settings screen. I couldn't use anything anymore and when closing and reopening Catima the home screen was so sluggish it became basically unusable.

My logs are being spammed so much I can't see what's happening. Is it maybe stuck in a loop in permission handling?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

There is a state bug with permission request in settings.kt I see remove the extra state or add a expection for it to prevent crashes in the future

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

That's a good catch. I also noticed slugginess but I didn't connect it to the repeated BT permissions check.

I've tried to make the logic a bit smarter here: 6fa800d

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That seems to work fine. It recovered and it doesn't crash even when declining the permission too often.

I'd say this is pretty much perfect now (I quite like how the Wear app will try to sync on resume, meaning that if you start the app or wake it up for sleep you see your cards while it syncs on the background), I just have one final nitpick on permission handling: if you deny the permission twice, the toggle will stay disabled with no feedback when you try to enable it again (because of Android blocking constant re-requesting). Can we maybe show a toast, just when tapping it on the settings activity when the permission is denied stating something like "Allow Catima access to nearby devices to sync with your smartwatch"? Or maybe a Snackbar would be even better, then you could open the permissions screen straight from that snackbar:

private fun navigateToSystemPermissionSetting() {
val permissionIntent = Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", getPackageName(), null)
)
permissionIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(permissionIntent)
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

There you go with your snackbar on denied sync permissions :) c64c3b8

… denied

Prevent the Wear OS sync switch from staying enabled after the user
denies the Bluetooth permission, which caused repeated permission
requests and UI sluggishness.

The setting is now only persisted as enabled once permission is granted,
and the service manager disables it on denial to break the request loop.

Addresses: CatimaLoyalty#3185 (comment)
@TheLastProject
TheLastProject changed the base branch from main to wip/wearos July 19, 2026 09:57
@TheLastProject
TheLastProject merged commit 3e970ba into CatimaLoyalty:wip/wearos Jul 19, 2026
4 checks passed
@TheLastProject

Copy link
Copy Markdown
Member

Thanks a lot for this. I do have some things I still want to deal with before going live with this (see #25 (comment)), but this part is approved at least, so I merged it into a new temporary wip/wearos branch.

Once we have these last fixes/features merged into the branch I think we can merge it into main and make a new release :)

@TheLastProject

Copy link
Copy Markdown
Member

Oh, I also forgot to test below Android 12, will still have to do so

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.

4 participants