Skip to content

Reaction: Per-device reaction settings#503

Merged
d4rken merged 28 commits into
mainfrom
feat/per-profile-reactions
Apr 14, 2026
Merged

Reaction: Per-device reaction settings#503
d4rken merged 28 commits into
mainfrom
feat/per-profile-reactions

Conversation

@d4rken

@d4rken d4rken commented Apr 14, 2026

Copy link
Copy Markdown
Member

What changed

Reaction settings (auto-play/pause, auto-connect, popup triggers) are now configured per AirPods device instead of globally. Each device profile stores its own reaction preferences, so users with multiple AirPods can have different behaviors for each pair. The separate global reaction settings screen has been removed — these settings now live directly in each device's settings page.

Technical Context

  • Reaction config moved from a global ReactionSettings datastore into ReactionConfig stored per AppleDeviceProfile. PlayPause, PopUpReaction, and AutoConnect now read settings from the matched device's profile instead of the global store.
  • LegacyReactionSettingsReader migrates existing global settings into the first profile on creation, so users upgrading don't lose their configuration.
  • DeviceSettingsViewModel switched from BluetoothAddress to profileId navigation, enabling settings to render before BT profile state arrives (fixes a blank-screen edge case).
  • PodCardComponents.kt (468 LOC monolith) split into focused per-component files (AncModeSelector, BatteryCapsule, SignalIndicator, etc.) — unrelated refactor bundled here because it touched the same card code.
  • PlayPause now filters reactions by profileId to prevent cross-device triggers (e.g., opening AirPods Max case no longer auto-pauses music configured only for AirPods Pro).
  • ~62 locale XML files have two deprecated global reaction strings removed.

Review checklist

  • Legacy migration path: verify LegacyReactionSettingsReader correctly reads old global settings and writes them into new per-profile config
  • Cross-device isolation: confirm PlayPause/PopUp/AutoConnect only fire for the device whose profile has them enabled
  • DeviceSettings navigation: profile-based lookup works when BT profile hasn't loaded yet (synthesized fallback)
  • No regressions in popup trigger timing — fresh broadcast timing logic changed

@d4rken d4rken added the enhancement Add a new feature of improve an existing feature label Apr 14, 2026
d4rken added 23 commits April 14, 2026 08:17
Migrate reaction toggles (auto-play, auto-pause, auto-connect, popups) from global ReactionSettings singleton to per-profile fields on AppleDeviceProfile. Each paired device can now have independent reaction behavior.

Extract ReactionConfig snapshot to decouple PodDevice from AppleDeviceProfile — reaction consumers read device.reactions instead of device.profile. Remove auto-connect from upgrade benefits (now free). Add LegacyReactionSettingsReader for one-shot DataStore migration.
Wrap setting sections in SettingsSection cards using Material 3 surfaceContainerLow surfaces with rounded corners. Reorder Controls by usage frequency, move Microphone Mode to Other section, and replace ear detection info row with a contextual info box that only appears for BLE-only connections.
Serialize AapConnection.send() with a dedicated sendMutex so two concurrent optimistic state updates cannot clobber each other. Collapse the OFF toggle click into a single combined VM call that runs both SetListeningModeCycle and SetAllowOffOption sequentially.
When both pods broadcast independently (one in case, one on desk), the pod inside the case carries authoritative case state via hasCaseContext bits. Previously, whichever address was processed last would overwrite the other, causing case state to flip-flop between OPEN and NOT_IN_CASE every scan cycle.

Two-layer fix: BlePodMonitor.processWithCache() now prefers the pod with case context when two scan results map to the same identity in one batch. ApplePodsFactory.getLatestCaseLidState() no longer treats NOT_IN_CASE as authoritative when recent history contains a broadcast with case context.
Move each composable from PodCardComponents.kt into its own file under a new cards/components/ subpackage. Add previews to each file. Also add getBatteryIcon() for Compose Material Icon battery levels used in the popup.
Hide the overlay popup when MainActivity is in the foreground since the user can already see battery info in the app. Show an info card when popups are enabled explaining they only appear outside the app. Show a warning card with a Fix button when monitor mode is MANUAL.

Refactor SettingsInfoBox into a reusable component with INFO/WARNING types and optional action slot.
PlayPause coerced null per-side ear values to false, making all ear states invisible when resolvedPrimaryPod was unknown. Fall back to AAP aggregate state (isBeingWorn/isEitherPodInEar) when per-side mapping is unavailable, keeping PodDevice.isLeftInEar/isRightInEar truthful for UI consumers. Add isEitherPodInEar to PlayPauseMonitorKey for proper dedup. Add diagnostic logging at the distinctUntilChangedBy boundary.
Remove Pre-distinct log that fired on every emission. Replace full PodDevice dump in Checking log with compact ear detection summary.
@d4rken d4rken force-pushed the feat/per-profile-reactions branch from e455e65 to 800c919 Compare April 14, 2026 06:49
@d4rken d4rken merged commit 7d08a44 into main Apr 14, 2026
10 checks passed
@d4rken d4rken deleted the feat/per-profile-reactions branch April 14, 2026 09:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Add a new feature of improve an existing feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant