# Spec: EQ Device Auto-Switch UX ## Delta for multi-device-eq ### MODIFIED Requirements #### Requirement: Startup device initialization When `eqMultiDeviceEnabled` is `true`, the system MUST query the current audio device via `obtenerDispositivoActual()` immediately after `_configurarSuscripcionDispositivo()` runs inside `cargarPersistido()`. The result MUST seed `_dispositivoActualId`, bootstrap a device preset if this is the first time the device is seen, and trigger `_resolverPresetActivo()` so the correct preset is applied before the first playback event. (Previously: `_dispositivoActualId` was null at startup; preset resolution required a reconnect event.) ##### Scenario: correct preset applied at startup — device known - GIVEN `eqMultiDeviceEnabled` is `true` - AND a preset for the current device ID already exists in `presetsDispositivo` - WHEN the app starts and `cargarPersistido()` completes - THEN `_dispositivoActualId` MUST be set to the current device ID - AND the device-resolved preset MUST be applied without any user action ##### Scenario: first-seen device bootstrapped at startup - GIVEN `eqMultiDeviceEnabled` is `true` - AND the current device has no existing entry in `presetsDispositivo` - WHEN `cargarPersistido()` runs - THEN the device MUST be bootstrapped with a copy of the currently resolved preset - AND `_dispositivoActualId` MUST be set to that device ID ##### Scenario: `obtenerDispositivoActual()` fails gracefully - GIVEN `eqMultiDeviceEnabled` is `true` - AND the platform channel call throws or returns null - WHEN `cargarPersistido()` runs - THEN `_dispositivoActualId` MUST fall back to `"builtin_speaker"` - AND no exception MUST propagate to callers ##### Scenario: toggle off — no device query at startup - GIVEN `eqMultiDeviceEnabled` is `false` - WHEN the app starts - THEN `obtenerDispositivoActual()` MUST NOT be called - AND EQ behavior MUST be identical to the pre-feature baseline --- ## New Capability: eq-device-rename ### Purpose Persistent custom names for audio devices, a connection status indicator per device row, and an edit modal combining rename input with embedded EQ editing. All gated by `eqMultiDeviceEnabled`. ### Requirements #### Requirement: Device name persistence The system MUST store a `Map` of `deviceId → customName` under the SharedPreferences key `eq_nombres_dispositivos_v1`. `EstadoEcualizador` MUST expose `renombrarDispositivo(id, nombre)` to write an entry and `obtenerNombreDispositivo(id)` to read one. Both operations MUST be reflected in `ServicioEcualizador`. ##### Scenario: rename persists across restarts - GIVEN the user renames device `"bt_a2dp:AA:BB"` to `"Living Room BT"` - WHEN the app is killed and relaunched - THEN `obtenerNombreDispositivo("bt_a2dp:AA:BB")` MUST return `"Living Room BT"` ##### Scenario: rename is immediately readable - GIVEN no custom name exists for a device - WHEN `renombrarDispositivo("bt_a2dp:AA:BB", "Office Speaker")` is called - THEN `obtenerNombreDispositivo("bt_a2dp:AA:BB")` MUST immediately return `"Office Speaker"` ##### Scenario: empty name is not persisted - GIVEN a device already has the custom name `"My Headset"` - WHEN `renombrarDispositivo(id, "")` is called - THEN the existing name MUST be preserved (empty names MUST NOT overwrite) ##### Scenario: feature toggle off — no name storage - GIVEN `eqMultiDeviceEnabled` is `false` - WHEN any rename API is called - THEN the operation MUST be a no-op and MUST NOT write to SharedPreferences --- #### Requirement: Display name fallback chain When displaying a device name anywhere in the UI, the system MUST resolve it using the following priority order, stopping at the first non-null, non-empty value: 1. Custom name from `eq_nombres_dispositivos_v1` 2. Platform name (`DispositivoAudio.nombre`) 3. Raw device ID ##### Scenario: custom name wins over platform name - GIVEN device has custom name `"Studio Monitors"` and platform name `"USB Audio"` - WHEN the device row is rendered - THEN `"Studio Monitors"` MUST be displayed ##### Scenario: platform name used when no custom name - GIVEN no custom name exists for device with platform name `"Sony WH-1000XM5"` - WHEN the device row is rendered - THEN `"Sony WH-1000XM5"` MUST be displayed ##### Scenario: device ID used as last resort - GIVEN no custom name and platform name is null or empty - WHEN the device row is rendered - THEN the raw device ID MUST be displayed (never an empty or null label) --- #### Requirement: Connection status indicator The Settings device list MUST display a visible connection indicator on each device row. The indicator MUST be green when the device ID matches `EstadoEcualizador.dispositivoActualId`, and absent (or neutral) otherwise. ##### Scenario: active device shows green indicator - GIVEN device `"bt_a2dp:AA:BB"` is the current active device - WHEN the device list is rendered in Settings - THEN that device row MUST show a green indicator - AND no other row MUST show a green indicator simultaneously ##### Scenario: indicator updates after device change - GIVEN device `"builtin_speaker"` was active and showing green - WHEN a BT device connects and becomes active - THEN the green indicator MUST move to the BT device row - AND the built-in speaker row MUST no longer show it ##### Scenario: no indicator when toggle is off - GIVEN `eqMultiDeviceEnabled` is `false` - WHEN the Settings screen is viewed - THEN no connection indicator MUST appear (device list section is hidden) --- #### Requirement: Device edit modal Tapping a device row in Settings MUST open a bottom sheet containing a rename TextField pre-filled with the device's resolved display name, and an embedded `EcualizadorWidget` scoped to that device's preset. The modal MUST use `showDragHandle: true` and `isScrollControlled: true`. ##### Scenario: modal opens on device row tap - GIVEN the device list is visible in Settings - WHEN the user taps a device row - THEN a bottom sheet MUST appear - AND it MUST contain a TextField pre-filled with the current display name (resolved via fallback chain) - AND it MUST contain an `EcualizadorWidget` showing that device's preset ##### Scenario: rename confirmed in modal persists - GIVEN the edit modal is open for device `"bt_a2dp:AA:BB"` - WHEN the user edits the name field to `"Bedroom Speaker"` and confirms - THEN `renombrarDispositivo("bt_a2dp:AA:BB", "Bedroom Speaker")` MUST be called - AND the device row MUST reflect the new name after the modal closes ##### Scenario: EQ changes in modal apply to device preset - GIVEN the edit modal is open for device `"bt_a2dp:AA:BB"` - WHEN the user adjusts a band in the embedded `EcualizadorWidget` - THEN the change MUST be saved to `presetsDispositivo["bt_a2dp:AA:BB"]` - AND the active EQ MUST update immediately if that device is currently active ##### Scenario: modal dismiss without save leaves state unchanged - GIVEN the edit modal is open and no changes have been confirmed - WHEN the user dismisses the sheet by dragging down - THEN the device name and preset MUST remain unchanged ##### Scenario: modal is scrollable to prevent EQ widget clipping - GIVEN the bottom sheet is open on a small screen - WHEN the user scrolls within the sheet - THEN the full `EcualizadorWidget` MUST be reachable without clipping