Persist the exploration, proposal, spec, design, tasks, and verify/archive reports produced during the multi-device EQ, alarm-countdown, and notification-visual-polish SDD cycles.
175 lines
7.2 KiB
Markdown
175 lines
7.2 KiB
Markdown
# 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<String, String>` 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
|