Files
pluriwave/openspec/changes/multi-device-eq/tasks.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

89 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tasks: Multi-Device Equalizer
## Review Workload Forecast
| Field | Value |
|-------|-------|
| Estimated changed lines | 700950 |
| 400-line budget risk | High |
| Chained PRs recommended | Yes |
| Suggested split | PR 1: Model + Service + Platform channel → PR 2: State layer + persistence → PR 3: UI + export-import |
| Delivery strategy | ask-on-risk |
| Chain strategy | pending |
Decision needed before apply: Yes
Chained PRs recommended: Yes
Chain strategy: pending
400-line budget risk: High
### Suggested Work Units
| Unit | Goal | Likely PR | Notes |
|------|------|-----------|-------|
| 1 | Platform bridge + Dart service + model + fakes | PR 1 | Base: feature/multi-device-eq; no behavioral changes yet |
| 2 | State layer 4-level resolution + persistence + toggle | PR 2 | Base: PR 1 branch; depends on Unit 1 |
| 3 | Settings UI + export/import v3 | PR 3 | Base: PR 2 branch; depends on Unit 2 |
---
## Phase 1: Foundation — Model and Service Interface (PR 1 scope)
- [ ] 1.1 RED: write `test/modelos/dispositivo_audio_test.dart` — assert `TipoDispositivo` enum values and `DispositivoAudio` equality
- [ ] 1.2 GREEN: create `lib/modelos/dispositivo_audio.dart``TipoDispositivo` enum + `DispositivoAudio` value class with `==`/`hashCode`
- [ ] 1.3 RED: write `test/servicios/servicio_dispositivo_audio_test.dart` — assert abstract contract + fake stream behavior
- [ ] 1.4 GREEN: create `lib/servicios/servicio_dispositivo_audio.dart` — abstract class with `dispositivoActual`, `onDispositivoCambiado`, `obtenerDispositivoActual()`, `dispose()`
- [ ] 1.5 GREEN: add `FakeServicioDispositivoAudio` to `test/helpers/fakes.dart``StreamController`-backed fake; `emitDispositivo(DispositivoAudio)` helper
- [ ] 1.6 REFACTOR: ensure fake satisfies all scenarios from spec `audio-device-detection / Fakeable service interface`
## Phase 2: Platform Channel — Android (PR 1 scope)
- [ ] 2.1 RED: write `test/servicios/servicio_dispositivo_audio_real_test.dart` — stub MethodChannel, assert `obtenerDispositivoActual()` maps `{id,type,name}` to `DispositivoAudio`
- [ ] 2.2 GREEN: implement `ServicioDispositivoAudioReal` in `lib/servicios/servicio_dispositivo_audio.dart` — MethodChannel `getActiveDevice` + EventChannel stream; map type int → `TipoDispositivo`
- [ ] 2.3 GREEN: add `pluriwave/audio_devices` EventChannel + MethodChannel in `android/app/src/main/kotlin/.../MainActivity.kt``AudioDeviceCallback` on API ≥ 23; stable key derivation (`bt_a2dp:<MAC>`, `wired_headset`, `builtin_speaker`, `usb_headset:<address>`)
- [ ] 2.4 REFACTOR: verify BT MAC key matches spec scenario `BT MAC key is stable across restarts`
## Phase 3: Platform Channel — iOS (PR 1 scope)
- [ ] 3.1 GREEN: create `ios/Runner/AudioDevicesPlugin.swift``AVAudioSession.routeChangeNotification` → EventChannel; derive `portType+uid` key; fallback to `portType+portName` per spec `iOS uid fallback`
- [ ] 3.2 GREEN: register `AudioDevicesPlugin` in `ios/Runner/AppDelegate.swift`
- [ ] 3.3 RED: write unit test asserting toggle-disabled path returns empty stream (uses fake, not real channel)
## Phase 4: EQ Service — Persistence Layer (PR 2 scope)
- [ ] 4.1 RED: extend `test/servicios/servicio_ecualizador_test.dart` — assert new SP key CRUD: `guardarPresetDispositivo`, `obtenerPresetDispositivo`, `guardarPresetMatriz`, `obtenerPresetMatriz`, `obtenerToggleMultiDispositivo`
- [ ] 4.2 GREEN: modify `lib/servicios/servicio_ecualizador.dart` — add SP keys `eq_multi_device_enabled_v1`, `eq_preset_por_dispositivo_v1`, `eq_presets_matriz_v1`; add typed read/write methods for device and matrix maps
- [ ] 4.3 GREEN: extend `ConfiguracionEcualizador` (in `servicio_ecualizador.dart`) — add `presetsDispositivo`, `presetsMatriz`, `eqMultiDeviceEnabled` fields; update `fromJson`/`toJson`
- [ ] 4.4 REFACTOR: validate storage size stays under 50 KB for 250 matrix entries per spec scenario
## Phase 5: State Layer — 4-Level Resolution (PR 2 scope)
- [ ] 5.1 RED: write failing tests in `test/estado/estado_ecualizador_test.dart` — 4-level resolution scenarios: matrix wins, falls back to station, falls back to device, falls back to global
- [ ] 5.2 GREEN: modify `lib/estado/estado_ecualizador.dart` — inject `ServicioDispositivoAudio`; add `presetsDispositivo`, `presetsMatriz`, `_dispositivoActual`, `eqMultiDeviceEnabled`; implement `_resolverPreset()` with 4-level hierarchy
- [ ] 5.3 RED: write failing tests — device stream subscription: `FakeServicioDispositivoAudio` emits device → assert `_resolverPreset()` re-runs and `notifyListeners()` fires
- [ ] 5.4 GREEN: subscribe to `ServicioDispositivoAudio.onDispositivoCambiado` in `EstadoEcualizador.init()` when toggle is enabled; call `aplicarPresetActivo()` on event
- [ ] 5.5 RED: write failing tests — first-seen device copies current resolved preset; subsequent reconnect does not overwrite
- [ ] 5.6 GREEN: implement first-device initialization: if `presetsDispositivo[deviceId]` is null, set it to current resolved preset and persist
- [ ] 5.7 RED: write failing tests — toggle-off path uses only 2-level resolution (station → global); no device subscription established
- [ ] 5.8 GREEN: guard all device logic behind `eqMultiDeviceEnabled` check in `EstadoEcualizador`
- [ ] 5.9 REFACTOR: ensure `_recrearPlayer()` / `aplicarPresetActivo()` path applies device-resolved preset, not stale `_presetActual`, per ADR-5
## Phase 6: Export/Import v3 (PR 3 scope)
- [ ] 6.1 RED: extend `test/servicios/servicio_export_import_test.dart` — v3 export includes `schemaVersion: 3`, `presetsPorDispositivo`, `presetsMatriz`; v3 round-trip preserves device presets; v2 import succeeds with empty device maps
- [ ] 6.2 GREEN: modify `lib/servicios/servicio_export_import.dart` — bump `schemaVersion` to 3; serialize/deserialize `presetsDispositivo` and `presetsMatriz`; handle missing fields from v1/v2 imports as empty maps
- [ ] 6.3 REFACTOR: confirm v4-future guard (unknown `schemaVersion` values do not crash)
## Phase 7: Settings UI (PR 3 scope)
- [ ] 7.1 RED: write widget test `test/pantallas/pantalla_ajustes_test.dart` — toggle off: `_SeccionEcualizadorAvanzado` absent; toggle on with devices: device list visible
- [ ] 7.2 GREEN: add `_SeccionEcualizadorAvanzado` widget in `lib/pantallas/pantalla_ajustes.dart``SwitchListTile` for `eqMultiDeviceEnabled`; `ListView` of known devices and their preset names, shown only when toggle is on and devices detected
- [ ] 7.3 REFACTOR: confirm widget is hidden (not just invisible) when toggle is off per spec scenario
## Phase 8: Integration Verification
- [ ] 8.1 Run `flutter test` — all new and modified tests must pass
- [ ] 8.2 Run `flutter analyze` — zero new warnings or errors
- [ ] 8.3 Run `dart format .` — no unformatted files
- [ ] 8.4 Manual smoke: toggle off → verify zero EQ behavior change vs. current release
- [ ] 8.5 Manual smoke: toggle on, connect BT device → verify EQ swap within 500ms