Reaction: Per-device reaction settings#503
Merged
Merged
Conversation
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.
e455e65 to
800c919
Compare
…plicit VM cleanup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
ReactionSettingsdatastore intoReactionConfigstored perAppleDeviceProfile.PlayPause,PopUpReaction, andAutoConnectnow read settings from the matched device's profile instead of the global store.LegacyReactionSettingsReadermigrates existing global settings into the first profile on creation, so users upgrading don't lose their configuration.DeviceSettingsViewModelswitched fromBluetoothAddresstoprofileIdnavigation, 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.profileIdto prevent cross-device triggers (e.g., opening AirPods Max case no longer auto-pauses music configured only for AirPods Pro).Review checklist
LegacyReactionSettingsReadercorrectly reads old global settings and writes them into new per-profile config