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:
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../estado/estado_radio.dart';
|
||||
import '../estado/estado_busqueda.dart';
|
||||
import '../l10n/gen/app_localizations.dart';
|
||||
import '../widgets/pluri_glass_surface.dart';
|
||||
import '../widgets/pluri_icon.dart';
|
||||
@@ -58,7 +58,7 @@ class _PantallaBuscarState extends State<PantallaBuscar> {
|
||||
|
||||
void _buscar() {
|
||||
final q = _controller.text.trim();
|
||||
context.read<EstadoRadio>().buscar(
|
||||
context.read<EstadoBusqueda>().buscar(
|
||||
nombre: q.isNotEmpty ? q : null,
|
||||
pais: _paisSeleccionado,
|
||||
idioma: _idiomaSeleccionado,
|
||||
@@ -68,7 +68,9 @@ class _PantallaBuscarState extends State<PantallaBuscar> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final estado = context.watch<EstadoRadio>();
|
||||
// S4-R3/S4-R5: this screen depends only on search state, so it watches
|
||||
// the dedicated notifier — playback events no longer rebuild it.
|
||||
final estado = context.watch<EstadoBusqueda>();
|
||||
final theme = Theme.of(context);
|
||||
final l10n = AppLocalizations.of(context);
|
||||
|
||||
@@ -85,7 +87,12 @@ class _PantallaBuscarState extends State<PantallaBuscar> {
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(PluriLayout.horizontal, 10, PluriLayout.horizontal, 0),
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
PluriLayout.horizontal,
|
||||
10,
|
||||
PluriLayout.horizontal,
|
||||
0,
|
||||
),
|
||||
child: PluriGlassSurface(
|
||||
padding: const EdgeInsets.all(10),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
@@ -132,7 +139,13 @@ class _PantallaBuscarState extends State<PantallaBuscar> {
|
||||
),
|
||||
_seccionFiltroInt(
|
||||
l10n.searchMinQualityFilterLabel,
|
||||
const [('64 kbps', 64), ('96 kbps', 96), ('128 kbps', 128), ('192 kbps', 192), ('320 kbps', 320)],
|
||||
const [
|
||||
('64 kbps', 64),
|
||||
('96 kbps', 96),
|
||||
('128 kbps', 128),
|
||||
('192 kbps', 192),
|
||||
('320 kbps', 320),
|
||||
],
|
||||
_calidadMinima,
|
||||
(v) {
|
||||
setState(() => _calidadMinima = v);
|
||||
@@ -144,7 +157,6 @@ class _PantallaBuscarState extends State<PantallaBuscar> {
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Widget _seccionFiltro(
|
||||
String titulo,
|
||||
List<(String, String)> opciones,
|
||||
@@ -153,7 +165,12 @@ class _PantallaBuscarState extends State<PantallaBuscar> {
|
||||
) {
|
||||
final theme = Theme.of(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(PluriLayout.horizontal, 8, PluriLayout.horizontal, 0),
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
PluriLayout.horizontal,
|
||||
8,
|
||||
PluriLayout.horizontal,
|
||||
0,
|
||||
),
|
||||
child: PluriGlassSurface(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
@@ -198,7 +215,12 @@ class _PantallaBuscarState extends State<PantallaBuscar> {
|
||||
) {
|
||||
final theme = Theme.of(context);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(PluriLayout.horizontal, 8, PluriLayout.horizontal, 0),
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
PluriLayout.horizontal,
|
||||
8,
|
||||
PluriLayout.horizontal,
|
||||
0,
|
||||
),
|
||||
child: PluriGlassSurface(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
@@ -235,16 +257,16 @@ class _PantallaBuscarState extends State<PantallaBuscar> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _resultados(EstadoRadio estado, ThemeData theme) {
|
||||
Widget _resultados(EstadoBusqueda estado, ThemeData theme) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
if (estado.cargandoBusqueda) {
|
||||
if (estado.cargando) {
|
||||
return const SizedBox(
|
||||
height: 220,
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
final resultados = estado.resultadosBusqueda;
|
||||
final resultados = estado.resultados;
|
||||
|
||||
if (resultados.isEmpty) {
|
||||
final sinFiltros =
|
||||
@@ -255,8 +277,7 @@ class _PantallaBuscarState extends State<PantallaBuscar> {
|
||||
height: 260,
|
||||
child: PluriEmptyState(
|
||||
glyph: PluriIconGlyph.search,
|
||||
title:
|
||||
sinFiltros ? l10n.searchEmptyTitle : l10n.searchNoResultsTitle,
|
||||
title: sinFiltros ? l10n.searchEmptyTitle : l10n.searchNoResultsTitle,
|
||||
subtitle:
|
||||
sinFiltros
|
||||
? l10n.searchEmptySubtitle
|
||||
@@ -265,7 +286,7 @@ class _PantallaBuscarState extends State<PantallaBuscar> {
|
||||
);
|
||||
}
|
||||
|
||||
final total = resultados.length + (estado.hayMasBusqueda ? 1 : 0);
|
||||
final total = resultados.length + (estado.hayMas ? 1 : 0);
|
||||
return ListView.separated(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
@@ -274,16 +295,16 @@ class _PantallaBuscarState extends State<PantallaBuscar> {
|
||||
separatorBuilder: (_, __) => const SizedBox(height: 10),
|
||||
itemBuilder: (context, i) {
|
||||
if (i >= resultados.length) {
|
||||
if (!estado.cargandoMasBusqueda) {
|
||||
Future<void>.microtask(estado.cargarMasBusqueda);
|
||||
if (!estado.cargandoMas) {
|
||||
Future<void>.microtask(estado.cargarMas);
|
||||
}
|
||||
return const Padding(
|
||||
padding: EdgeInsets.all(18),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
if (i >= resultados.length - 5 && estado.hayMasBusqueda) {
|
||||
Future<void>.microtask(estado.cargarMasBusqueda);
|
||||
if (i >= resultados.length - 5 && estado.hayMas) {
|
||||
Future<void>.microtask(estado.cargarMas);
|
||||
}
|
||||
return TarjetaEmisora(
|
||||
emisora: resultados[i],
|
||||
|
||||
Reference in New Issue
Block a user