feat(favoritos): replace stacked group panels with chip-filtered reorderable list

Replaces the stacked per-group panel layout with a single
chip-filtered flat list. Chips read "{name} · {count}" (new ARB keys
favoriteGroupsChipLabel/favoritesFilterAllLabel), one per group plus
an "All" chip. Rows drag-reorder via a leading handle
(ReorderableDragStartListener, buildDefaultDragHandles: false) using
the modern onReorderItem callback rather than the now-@Deprecated
onReorder (Flutter 3.44 marks it obsolete).

EstadoRadio additions: listaFavoritosManual (a new memoized getter
returning the stored order untouched by the global ordenListas
setting - listaFavoritos itself always re-sorts by
name/quality on every read, which would silently discard any
drag-to-reorder), reordenarFavorito (thin wrapper over the
already-existing ServicioFavoritos.reordenar, previously unused
outside its own service test), and ordenarFavoritos (applies an
existing OrdenEmisoras criterion via ordenarEmisoras() and persists
the result as the new manual order, so the swap_vert sort action's
result also survives a restart). listaFavoritos itself is untouched,
so Android Auto's tree and the future Escuchar grid (WU5) are
unaffected by Favoritos' own manual order.

Group management: an "Manage lists" action chip pushes the existing
PantallaAjustesGruposFavoritos screen (Settings' own screen, reused
rather than duplicated) - a second entry point to the same screen.
Custom-station CTA: a new dashed-bordered card opens the add-station
form directly; that form was renamed from private _FormularioEmisora
to public FormularioEmisoraPersonalizada in
pantalla_ajustes_emisoras_personalizadas.dart so both screens share
one implementation. New ARB keys: favoriteGroupsManage,
customStationsAddCta.

Tests: pantalla_favoritos_plural_test.dart (the file tasks.md named)
never imported PantallaFavoritos - it only covers stationCount's ARB
plural formatting, unrelated to this screen. Left it untouched and
added test/pantallas/pantalla_favoritos_test.dart instead: 3
state-layer tests for the new EstadoRadio surface plus 6 widget
scenarios (empty-state CTA, chip filter, drag-reorder persistence,
sort action, group management + chip reactivity, custom-station
CTA). 604 -> 614 tests (2 skipped, unchanged). flutter analyze
unchanged at 1 pre-existing info.

Recorded in tasks.md with the test-file correction and the
design decisions this WU had to make on its own (no ADR covers
Favoritos' manual-order persistence).
This commit is contained in:
2026-07-28 23:49:06 +02:00
parent ebdde7df01
commit 504a13641f
21 changed files with 1011 additions and 126 deletions
+36
View File
@@ -164,6 +164,7 @@ class EstadoRadio extends ChangeNotifier {
final _memoPopulares = MemoLista<Emisora>();
final _memoTendencias = MemoLista<Emisora>();
final _memoFavoritos = MemoLista<Emisora>();
final _memoFavoritosManual = MemoLista<Emisora>();
final _memoGrupos = MemoLista<GrupoFavoritos>();
final _memoCustom = MemoLista<Emisora>();
final _memoInicio = MemoLista<Emisora>();
@@ -200,6 +201,17 @@ class EstadoRadio extends ChangeNotifier {
_listaFavoritos,
_ordenListas,
], () => ordenarEmisoras(_listaFavoritos, _ordenListas));
/// WU4, `favorites-organization` spec: Favoritos' own manual order —
/// unlike [listaFavoritos], this is NOT re-sorted by the global
/// [ordenListas] setting on every read. It reflects exactly the order
/// stored via the `orden` column (`ServicioFavoritos.obtenerTodos()`),
/// which [reordenarFavorito] and [ordenarFavoritos] mutate. Other
/// consumers of `listaFavoritos` (Android Auto, the Escuchar grid) are
/// unaffected by drag-reordering Favoritos.
List<Emisora> get listaFavoritosManual => _memoFavoritosManual.obtener([
_listaFavoritos,
], () => List<Emisora>.unmodifiable(_listaFavoritos));
List<GrupoFavoritos> get gruposFavoritos => _memoGrupos.obtener([
_gruposFavoritos,
], () => List<GrupoFavoritos>.unmodifiable(_gruposFavoritos));
@@ -371,6 +383,30 @@ class EstadoRadio extends ChangeNotifier {
await cargarFavoritos();
}
/// WU4: persists a single drag-to-reorder move within [listaFavoritosManual].
/// [nuevoIndice] is the absolute target position among ALL favorites (not
/// scoped to any chip filter) — the screen translates a filtered-list drag
/// into this global index before calling this method.
Future<void> reordenarFavorito(String uuid, int nuevoIndice) async {
await favoritos.reordenar(uuid, nuevoIndice);
await cargarFavoritos();
}
/// WU4: the Favoritos `swap_vert` sort action. Reuses the existing
/// [OrdenEmisoras] criteria and [ordenarEmisoras] function — "It MUST NOT
/// introduce a sort criterion with no backing implementation"
/// (favorites-organization spec). Unlike [ordenListas] (a GLOBAL setting
/// affecting favorites/searches/nearby/quick-lists), this sorts ONLY the
/// favorites list once and PERSISTS the result as the new manual order —
/// consistent with [reordenarFavorito]'s "the order MUST persist" contract.
Future<void> ordenarFavoritos(OrdenEmisoras criterio) async {
final ordenados = ordenarEmisoras(_listaFavoritos, criterio);
for (var i = 0; i < ordenados.length; i++) {
await favoritos.reordenar(ordenados[i].uuid, i);
}
await cargarFavoritos();
}
Future<void> cambiarEmisoraPreferida(Emisora? emisora) async {
_emisoraPreferidaUuid = emisora?.uuid;
final prefs = await _resolverPrefs();