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
+55
View File
@@ -0,0 +1,55 @@
import '../modelos/emisora.dart';
/// User-selectable ordering for every station list in the app.
enum OrdenEmisoras { nombre, calidad }
/// Returns a sorted COPY of [emisoras] according to [orden].
List<Emisora> ordenarEmisoras(List<Emisora> emisoras, OrdenEmisoras orden) {
final ordenadas = List<Emisora>.from(emisoras);
switch (orden) {
case OrdenEmisoras.nombre:
ordenadas.sort(
(a, b) => a.nombre.toLowerCase().compareTo(b.nombre.toLowerCase()),
);
case OrdenEmisoras.calidad:
ordenadas.sort((a, b) {
final porBitrate = (b.bitrate ?? 0).compareTo(a.bitrate ?? 0);
if (porBitrate != 0) return porBitrate;
return 0;
});
}
return ordenadas;
}
/// Identity-memoized derived list (S4-R5).
///
/// Derived-list getters used to return a fresh copy on every read, which made
/// `context.select` rebuild on EVERY notification (lists compare by identity).
/// This memo recomputes only when one of the source [claves] changes identity,
/// so unrelated notifications (e.g. audio buffer events) stop rebuilding the
/// screens that select these lists.
class MemoLista<T> {
List<Object?>? _claves;
List<T>? _resultado;
List<T> obtener(List<Object?> claves, List<T> Function() calcular) {
final anteriores = _claves;
final resultado = _resultado;
if (anteriores != null &&
resultado != null &&
anteriores.length == claves.length) {
var iguales = true;
for (var i = 0; i < claves.length; i++) {
if (!identical(anteriores[i], claves[i])) {
iguales = false;
break;
}
}
if (iguales) return resultado;
}
final nuevo = calcular();
_claves = List<Object?>.of(claves);
_resultado = nuevo;
return nuevo;
}
}