# Tasks: Multi-Device Equalizer ## Review Workload Forecast | Field | Value | |-------|-------| | Estimated changed lines | 700–950 | | 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:`, `wired_headset`, `builtin_speaker`, `usb_headset:
`) - [ ] 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