Files
pluriwave/openspec/changes/eq-device-autoswitch-ux/spec.md
T
FreeTLab bccc5c48b8
Build & Deploy PluriWave / Análisis de código (push) Successful in 38s
Build & Deploy PluriWave / Build APK + AAB release (push) Successful in 2m30s
docs(openspec): add SDD artifact trail for recent alarm and EQ changes
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.
2026-07-04 12:42:11 +02:00

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