refactor(state): extract recording and search state, scope screen rebuilds

- New EstadoGrabacion owns the recording service, subscription, directory/size preferences and open-file actions
- New EstadoBusqueda owns search, nearby stations, pagination and the min-bitrate filter
- New orden_emisoras.dart with the OrdenEmisoras enum, shared sorter and list identity memoization so context.select comparisons work on derived lists
- Large screens (inicio, buscar, favoritos, ajustes, reproductor) consume scoped selects/dedicated notifiers instead of root context.watch<EstadoRadio>, so audio buffer events no longer rebuild whole screens
- Remove all 15 TODO(S4b) compat members from EstadoRadio; consumers use the dedicated providers. EstadoRadio drops from ~1121 to 753 lines, keeping playback/stations/favorites orchestration
- 8 new tests including a rebuild-scoping probe (110 total green), flutter analyze clean
This commit is contained in:
2026-06-11 21:43:18 +02:00
parent 0416b301b2
commit 52855e75c2
17 changed files with 1195 additions and 643 deletions
@@ -329,25 +329,25 @@ Chain strategy: N/A (local apply)
### S4b pre-work: write failing tests
- [ ] **T-S4b-01** [RED] Create `test/estado/estado_grabacion_test.dart`: `ServicioGrabacionRadio` is managed by `EstadoGrabacion`; notifies listeners on recording state change. (S4-R2) **~20 lines.**
- [ ] **T-S4b-02** [RED] Create `test/estado/estado_busqueda_test.dart`: search query update notifies `EstadoBusqueda` listeners. (S4-R3) **~15 lines.**
- [ ] **T-S4b-03** [RED] Add widget test: changing EQ preset does NOT rebuild `PantallaInicio` (S4-R5-A). **~20 lines.**
- [x] **T-S4b-01** [RED] Create `test/estado/estado_grabacion_test.dart`: `ServicioGrabacionRadio` is managed by `EstadoGrabacion`; notifies listeners on recording state change. (S4-R2) **DONE — 4 tests: notify-on-state-change, iniciar delegates with current station, no-station → alError without service call, service error state → alError.**
- [x] **T-S4b-02** [RED] Create `test/estado/estado_busqueda_test.dart`: search query update notifies `EstadoBusqueda` listeners. (S4-R3) **DONE — 3 tests: notify on buscar, pagination/memory cap (moved from estado_radio_test), identity-stable `resultados` getter (S4-R5 enabler).**
- [x] **T-S4b-03** [RED] Add widget test: changing EQ preset does NOT rebuild `PantallaInicio` (S4-R5-A). **DONE — `test/pantallas/pantalla_inicio_rebuild_test.dart` via `debugPrintRebuildDirtyWidgets` log probe (dirty-flag probe is invalid: provider defers dependent notification to the next build phase) + positive control (cargarPopulares DOES rebuild).**
### S4b implementation
- [ ] **T-S4b-04** [GREEN] Create `lib/estado/estado_grabacion.dart`: `EstadoGrabacion extends ChangeNotifier` — owns recording state + `_escucharGrabacion` subscription (currently `estado_radio.dart:51, :79`). Register in `MultiProvider`. **Reqs:** S4-R2. **~80 lines.**
- [ ] **T-S4b-05** [GREEN] Create `lib/estado/estado_busqueda.dart`: `EstadoBusqueda extends ChangeNotifier` — owns search query, results, loading state. Register in `MultiProvider`. **Reqs:** S4-R3. **~60 lines.**
- [ ] **T-S4b-06** [GREEN] Edit `lib/pantallas/pantalla_inicio.dart` (line 43): replace root `context.watch<EstadoRadio>()` with `context.select` / `Consumer` scoped to fields it actually reads. **Reqs:** S4-R5. **~30 lines.**
- [ ] **T-S4b-07** [GREEN] Edit `lib/pantallas/pantalla_ajustes.dart` (~6 watch sites): replace each `context.watch<EstadoRadio>()` with scoped `context.select` / `Consumer` for the specific field. **Reqs:** S4-R5. **~40 lines.**
- [ ] **T-S4b-08** [GREEN] Edit `lib/pantallas/pantalla_favoritos.dart`: scope the `EstadoRadio` watch. **Reqs:** S4-R5. **~15 lines.**
- [ ] **T-S4b-09** [GREEN] Edit `lib/estado/estado_radio.dart`: remove EQ, recording, and search state fields/methods; remove backward-compatible getters added in S4a (they carried `// TODO(S4b): remove getter` comments). **Reqs:** S4-R1, S4-R2, S4-R3. **~80 lines removed.**
- [x] **T-S4b-04** [GREEN] Create `lib/estado/estado_grabacion.dart`: `EstadoGrabacion extends ChangeNotifier` — owns recording state + `_escucharGrabacion` subscription (currently `estado_radio.dart:51, :79`). Register in `MultiProvider`. **Reqs:** S4-R2. **DONE — owns ServicioGrabacionRadio, the state subscription, dir/maxBytes/open-file actions and the `pluriwave/file_actions` MethodChannel; `emisoraActual` + `alError` callback seams (mirrors S4a). ListenableProvider in app.dart.**
- [x] **T-S4b-05** [GREEN] Create `lib/estado/estado_busqueda.dart`: `EstadoBusqueda extends ChangeNotifier` — owns search query, results, loading state. Register in `MultiProvider`. **Reqs:** S4-R3. **DONE — also owns nearby-stations (cercanas) lookup and min-bitrate filter (they shared search state); `ordenListas`/`textos`/`alError` callback seams. ListenableProvider in app.dart.**
- [x] **T-S4b-06** [GREEN] Edit `lib/pantallas/pantalla_inicio.dart` (line 43): replace root `context.watch<EstadoRadio>()` with `context.select` / `Consumer` scoped to fields it actually reads. **Reqs:** S4-R5. **DONE — selects over identity-memoized getters (NEW `lib/estado/orden_emisoras.dart` MemoLista); cercanas/genre-search sections consume EstadoBusqueda.**
- [x] **T-S4b-07** [GREEN] Edit `lib/pantallas/pantalla_ajustes.dart` (~6 watch sites): replace each `context.watch<EstadoRadio>()` with scoped `context.select` / `Consumer` for the specific field. **Reqs:** S4-R5. **DONE — Grabaciones → watch<EstadoGrabacion>; Timer/Orden/Grupos/Preferida/Emisoras → context.select; _SeccionInfo keeps its scoped Consumer.**
- [x] **T-S4b-08** [GREEN] Edit `lib/pantallas/pantalla_favoritos.dart`: scope the `EstadoRadio` watch. **Reqs:** S4-R5. **DONE — selects listaFavoritos + gruposFavoritos. ALSO: pantalla_buscar root watch → watch<EstadoBusqueda>; pantalla_reproductor `_GrabacionWidget` → watch<EstadoGrabacion> (required: EstadoRadio no longer notifies on recording/search).**
- [x] **T-S4b-09** [GREEN] Edit `lib/estado/estado_radio.dart`: remove EQ, recording, and search state fields/methods; remove backward-compatible getters added in S4a (they carried `// TODO(S4b): remove getter` comments). **Reqs:** S4-R1, S4-R2, S4-R3. **DONE — all 15 compat members removed (zero TODO(S4b) in lib/); recording + search state/methods extracted; EstadoRadio 1121 (pre-split) → 753 lines, focused on playback/stations/favorites orchestration.**
### S4b verification
- [ ] **T-S4b-10** Run `flutter test test/estado/estado_grabacion_test.dart test/estado/estado_busqueda_test.dart` plus the rebuild scope test.
- [ ] **T-S4b-11** Run `flutter test` (full suite) — no regressions.
- [ ] **T-S4b-12** Run `flutter analyze`zero errors.
- [ ] **T-S4b-13** Run `dart format lib/estado/estado_grabacion.dart lib/estado/estado_busqueda.dart lib/estado/estado_radio.dart lib/pantallas/pantalla_inicio.dart lib/pantallas/pantalla_ajustes.dart lib/pantallas/pantalla_favoritos.dart`.
- [x] **T-S4b-10** Run `flutter test test/estado/estado_grabacion_test.dart test/estado/estado_busqueda_test.dart` plus the rebuild scope test — 8/8 green (RED captured first: `+0 -3` load failures).
- [x] **T-S4b-11** Run `flutter test` (full suite) — 110/110 passing (103 baseline 1 moved pagination test + 8 new), no regressions.
- [x] **T-S4b-12** Run `flutter analyze``No issues found!`.
- [x] **T-S4b-13** Run `dart format` on all 15 touched Dart files (10 reflowed); analyze + suite re-run after format.
### S4b Definition of Done
- `flutter test` green.