Skip to content

Refactor pairing process and enhance camera discovery with model name probing#11

Open
rock3r wants to merge 2 commits intomasterfrom
better-pairing
Open

Refactor pairing process and enhance camera discovery with model name probing#11
rock3r wants to merge 2 commits intomasterfrom
better-pairing

Conversation

@rock3r
Copy link
Owner

@rock3r rock3r commented Feb 8, 2026

Redesigns the camera pairing flow from a single-step screen into a multi-phase, state-driven experience with real-time camera discovery, model name probing, and granular user feedback through each step.

What changed

New pairing flow

The pairing screen now walks the user through distinct phases, each with its own UI:

  1. Scanning — BLE discovery with animated pulsing icon (empty state) or a live device list showing detected cameras. Model names are probed concurrently over temporary GATT connections.
  2. Associating — System Companion Device Manager permission prompt.
  3. Bonding — OS-level Bluetooth pairing with polling and 60-second timeout.
  4. Connecting — Vendor-specific pairing initialization.
  5. Success — Confirmation with 5-second auto-close timer.
  6. Error — Categorized errors (rejected/timeout/unknown) with retry and cancel options.

All intermediate states now include a cancel button so the user is never stuck.

Architecture improvements

  • CompanionDeviceManagerHelper extracted to an interface with AndroidCompanionDeviceManagerHelper implementation, following the existing BluetoothBondingChecker pattern.
  • PairingViewModel now injects both ioDispatcher and mainDispatcher, eliminating hardcoded Dispatchers.Main and making all coroutine timing fully controllable in tests.
  • Bonding timeout replaced System.currentTimeMillis() loop with withTimeout(), enabling virtual-time testing.
  • Duplicated code consolidated: exception handling in performPairingConnection, shared tail in removeBondAndRetry, and disconnect cleanup blocks.
  • Race condition fixed in probeModelName — job is now stored in the map before the coroutine body can execute.
  • UI composables unified under PairingStateScaffold for consistent layout across Associating, Bonding, Connecting, and Success states.

Vendor changes

  • CameraVendor gains supportsModelName capability flag and extractModelFromPairingName() for fallback model extraction.
  • RicohGattSpec and SonyGattSpec add device name characteristic UUIDs for model probing.
  • KableCameraRepository and KableCameraConnection support readModelName() via GATT.

Cleanup

  • Removed PairingComponents.kt (417 lines) — replaced by per-state composables.
  • Removed 13 unused string resources from the old flow.
  • Removed stale comments, redundant UI info (model name shown twice in device rows), and fragile Spacer(weight) usage.
  • Feedback link upgraded from raw Modifier.clickable to TextButton for proper accessibility.

Test coverage

64 pairing tests (up from 26):

Suite Tests Coverage
PairingViewModelTest 28 State machine transitions, bonding timeout, navigation events (manual close + 5s auto-close), error mapping (reject/timeout/unknown), association edge cases (null data, wrong state), initializePairing failure, already-paired filtering, feedback content
PairingComponentsTest 23 All content composables, callback invocations, error variant text, cancel button presence/absence, scanning pill indicator, mixed Detecting/Detected list, long device names
PairingScreenTest 13 Full Robolectric integration: state-to-content mapping for all 6 states, top bar and feedback bar persistence across states, back navigation, feedback click, cancel-from-error flow

New test infrastructure:

  • FakeCompanionDeviceManagerHelper replaces mockk usage
  • WriteRecorder utility for GATT write verification
  • Test AndroidManifest.xml and TestActivity for Robolectric Compose tests

Note

Medium Risk
Medium risk because it rewires the BLE pairing state machine (scanning/association/bonding/connecting) and adds temporary probe connections, which could affect device discovery timing and pairing reliability across vendors.

Overview
Refactors PairingScreen/PairingViewModel from a single-step flow into a multi-phase, state-driven pairing experience (scanning → associating → bonding → connecting → success/error), with cancel/retry paths and updated strings/UI (new per-state composables + shared PairingStateScaffold, replaces PairingComponents.kt).

Enhances discovery by probing camera model names via temporary GATT connections (bounded concurrency + timeouts), adding CameraConnection.readModelName()/CameraRepository support and vendor wiring (supportsModelName capability + new GATT UUIDs for Ricoh/Sony).

Improves DI/testability by introducing dispatcher qualifiers (@IoDispatcher, @MainDispatcher), extracting CompanionDeviceManagerHelper into an interface with AndroidCompanionDeviceManagerHelper (optionally targets a specific MAC), and expanding unit/Robolectric + Compose UI test infrastructure (Gradle test deps/options, test manifest/activity, new fakes/utilities).

Written by Cursor Bugbot for commit 84a8062. This will update automatically on new commits. Configure here.

@rock3r rock3r force-pushed the better-pairing branch 2 times, most recently from 74d17b2 to 60b6214 Compare February 9, 2026 08:30
Replace the single-step pairing screen with a multi-phase flow
that scans for nearby cameras, probes model names over BLE, and
guides the user through association, bonding, and connection with
per-state UI and clear error handling.

Core changes:
- Add scanning phase with real-time BLE discovery and concurrent
  model name probing via GATT device name characteristic
- Split monolithic PairingComponents into per-state composables
  (Scanning, Associating, Bonding, Connecting, Success, Error)
  backed by a shared PairingStateScaffold
- Extend CameraVendor with model name capability and add GATT
  characteristic UUIDs to Ricoh and Sony vendors
- Extract CompanionDeviceManagerHelper interface for testability;
  target specific MAC addresses during system association
- Add cancel buttons to all intermediate pairing states

ViewModel improvements:
- Replace System.currentTimeMillis() bonding timeout with
  coroutine withTimeout for virtual-time testability
- Inject mainDispatcher to eliminate hardcoded Dispatchers.Main
- Consolidate duplicated exception handling in connection logic
- Fix probeModelName race condition in job map assignment
- Extract shared code from removeBondAndRetry branches

UI polish:
- Replace Modifier.clickable feedback link with accessible
  TextButton
- Remove redundant model name from device row supporting text
- Clean up 13 unused string resources from old pairing flow

Test coverage (64 pairing tests, up from 26):
- Add PairingScreenTest: 13 Robolectric integration tests for
  full-screen state rendering, chrome persistence, navigation
- Extend PairingViewModelTest to 28 tests covering bonding
  timeout, navigation events, auto-close timer, error mapping,
  association edge cases, and feedback content
- Extend PairingComponentsTest to 23 tests covering callback
  invocations, error variants, cancel buttons, scanning pill,
  and mixed device list rendering
- Add FakeCompanionDeviceManagerHelper replacing mockk usage
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Replace the single-step pairing screen with a multi-phase flow
that scans for nearby cameras, probes model names over BLE, and
guides the user through association, bonding, and connection with
per-state UI and clear error handling.

Core changes:
- Add scanning phase with real-time BLE discovery and concurrent
  model name probing via GATT device name characteristic
- Split monolithic PairingComponents into per-state composables
  (Scanning, Associating, Bonding, Connecting, Success, Error)
  backed by a shared PairingStateScaffold
- Extend CameraVendor with model name capability and add GATT
  characteristic UUIDs to Ricoh and Sony vendors
- Extract CompanionDeviceManagerHelper interface for testability;
  target specific MAC addresses during system association
- Add cancel buttons to all intermediate pairing states

ViewModel improvements:
- Replace System.currentTimeMillis() bonding timeout with
  coroutine withTimeout for virtual-time testability
- Inject mainDispatcher to eliminate hardcoded Dispatchers.Main
- Consolidate duplicated exception handling in connection logic
- Fix probeModelName race condition in job map assignment
- Extract shared code from removeBondAndRetry branches

UI polish:
- Replace Modifier.clickable feedback link with accessible
  TextButton
- Remove redundant model name from device row supporting text
- Clean up 13 unused string resources from old pairing flow

Test coverage (64 pairing tests, up from 26):
- Add PairingScreenTest: 13 Robolectric integration tests for
  full-screen state rendering, chrome persistence, navigation
- Extend PairingViewModelTest to 28 tests covering bonding
  timeout, navigation events, auto-close timer, error mapping,
  association edge cases, and feedback content
- Extend PairingComponentsTest to 23 tests covering callback
  invocations, error variants, cancel buttons, scanning pill,
  and mixed device list rendering
- Add FakeCompanionDeviceManagerHelper replacing mockk usage
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