From dcd848887469cb56a53e6f876e12af457d43a205 Mon Sep 17 00:00:00 2001 From: freetlab Date: Thu, 30 Jul 2026 16:22:12 +0200 Subject: [PATCH] fix(favoritos): header actions row, chip colours Audit 4.1 (t4:216): the manage-groups (create_new_folder) and sort (swap_vert) actions now live in PluriRootHeader's own actions slot as two icon buttons -- were an ActionChip inside the chip strip plus a PopupMenuButton sharing a Row with it. The prototype's own back arrow stays absent (binding decision: this root keeps its bottom tab bar, unlike the prototype's pushed-with-back-arrow shape) -- this closes the remaining gap in what was a partial fix. Audit 4.2 (t4:219-221): group filter chips are solid brand teal with dark text when active, listSurface + a faint border when not -- was Material's own ChoiceChip theming (electricMagenta@24% selected). Updated pantalla_favoritos_test.dart's two "Manage lists" text finders to locate the relocated action by key instead (the action is now an icon-only IconButton with a tooltip, not a labelled chip). --- lib/pantallas/pantalla_favoritos.dart | 97 +++++++++++++-------- test/pantallas/pantalla_favoritos_test.dart | 38 +++++++- 2 files changed, 97 insertions(+), 38 deletions(-) diff --git a/lib/pantallas/pantalla_favoritos.dart b/lib/pantallas/pantalla_favoritos.dart index 35b8306..471f27b 100644 --- a/lib/pantallas/pantalla_favoritos.dart +++ b/lib/pantallas/pantalla_favoritos.dart @@ -6,6 +6,7 @@ import '../l10n/display_names.dart'; import '../l10n/gen/app_localizations.dart'; import '../modelos/emisora.dart'; import '../modelos/grupo_favoritos.dart'; +import '../tema/pluriwave_tokens.dart'; import '../widgets/fila_emisora_plana.dart'; import '../widgets/pluri_icon.dart'; import '../widgets/pluri_layout.dart'; @@ -186,22 +187,23 @@ class _PantallaFavoritosState extends State { children: [ // S1/S2 (Tier 1 visual fidelity): see the empty-state branch // above — PluriScreenHeader is retired everywhere. + // + // Audit 4.1 (t4:216): the prototype's two header icon actions + // (create_new_folder, swap_vert) now live in PluriRootHeader's + // own actions slot -- they used to be scattered as an + // ActionChip inside the chip strip and a PopupMenuButton + // sharing a Row with it. The back arrow the prototype also + // draws stays absent (binding decision: this root keeps its + // bottom tab bar, unlike the prototype's own pushed shape). PluriRootHeader( title: l10n.favoritesTitle, onSleepTimer: () => showPluriSleepTimerSheet(context), - ), - const SizedBox(height: 12), - Row( - children: [ - Expanded( - child: _FilaChipsGrupos( - grupos: gruposVisibles, - favoritos: favoritos, - seleccionado: seleccionEfectiva, - onSeleccionar: - (id) => setState(() => _grupoSeleccionadoId = id), - onGestionar: _abrirGestionDeListas, - ), + actions: [ + IconButton( + key: const ValueKey('favorites-manage-groups-action'), + icon: const Icon(Icons.create_new_folder_rounded), + tooltip: l10n.favoriteGroupsManage, + onPressed: _abrirGestionDeListas, ), PopupMenuButton( icon: const Icon(Icons.swap_vert_rounded), @@ -221,6 +223,13 @@ class _PantallaFavoritosState extends State { ), ], ), + const SizedBox(height: 12), + _FilaChipsGrupos( + grupos: gruposVisibles, + favoritos: favoritos, + seleccionado: seleccionEfectiva, + onSeleccionar: (id) => setState(() => _grupoSeleccionadoId = id), + ), ], ), ), @@ -256,18 +265,45 @@ class _FilaChipsGrupos extends StatelessWidget { required this.favoritos, required this.seleccionado, required this.onSeleccionar, - required this.onGestionar, }); final List grupos; final List favoritos; final String? seleccionado; final ValueChanged onSeleccionar; - final VoidCallback onGestionar; String _nombreVisible(AppLocalizations l10n, GrupoFavoritos grupo) => grupo.esSinAsignar ? l10n.favoriteGroupsUnassigned : grupo.nombre; + /// Audit 4.2 (t4:219-221): active `#21D4D9`/`#062126` w800, inactive + /// `listSurface` + a faint border / w700 -- was Material's own + /// `ChoiceChip` theming (a plain checkbox-style selected fill). + Widget _chip({ + required String label, + required bool selected, + required VoidCallback onTap, + }) { + return ChoiceChip( + label: Text(label), + labelStyle: TextStyle( + fontWeight: selected ? FontWeight.w800 : FontWeight.w700, + color: selected ? const Color(0xFF062126) : const Color(0xFFF2F7FA), + ), + selected: selected, + showCheckmark: false, + selectedColor: PluriWaveTokens.brand, + backgroundColor: PluriWaveTokens.dark.listSurface, + side: BorderSide( + color: + selected + ? Colors.transparent + : Colors.white.withValues(alpha: 0.09), + ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + onSelected: (_) => onTap(), + ); + } + @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); @@ -278,38 +314,27 @@ class _FilaChipsGrupos extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.only(right: 8), - child: ChoiceChip( - label: Text( - l10n.favoriteGroupsChipLabel( - l10n.favoritesFilterAllLabel, - favoritos.length, - ), + child: _chip( + label: l10n.favoriteGroupsChipLabel( + l10n.favoritesFilterAllLabel, + favoritos.length, ), selected: seleccionado == null, - onSelected: (_) => onSeleccionar(null), + onTap: () => onSeleccionar(null), ), ), for (final grupo in grupos) Padding( padding: const EdgeInsets.only(right: 8), - child: ChoiceChip( - label: Text( - l10n.favoriteGroupsChipLabel( - _nombreVisible(l10n, grupo), - favoritos - .where((e) => e.grupoFavoritosId == grupo.id) - .length, - ), + child: _chip( + label: l10n.favoriteGroupsChipLabel( + _nombreVisible(l10n, grupo), + favoritos.where((e) => e.grupoFavoritosId == grupo.id).length, ), selected: seleccionado == grupo.id, - onSelected: (_) => onSeleccionar(grupo.id), + onTap: () => onSeleccionar(grupo.id), ), ), - ActionChip( - avatar: const Icon(Icons.add_rounded, size: 18), - label: Text(l10n.favoriteGroupsManage), - onPressed: onGestionar, - ), ], ), ); diff --git a/test/pantallas/pantalla_favoritos_test.dart b/test/pantallas/pantalla_favoritos_test.dart index a061589..0d14502 100644 --- a/test/pantallas/pantalla_favoritos_test.dart +++ b/test/pantallas/pantalla_favoritos_test.dart @@ -357,7 +357,12 @@ void main() { await tester.pumpWidget(buildScreen(estado)); await pumpStable(tester); - await tester.tap(find.text('Manage lists')); + // Audit 4.1 (t4:216): the manage-groups action moved into the + // header as a create_new_folder icon button, replacing the old + // "Manage lists" ActionChip in the chip strip. + await tester.tap( + find.byKey(const ValueKey('favorites-manage-groups-action')), + ); await pumpStable(tester); // "Group Management Reachable from Favoritos": the SAME screen @@ -385,7 +390,10 @@ void main() { await tester.pumpAndSettle(); expect(find.byType(PantallaAjustesGruposFavoritos), findsNothing); - expect(find.text('Manage lists'), findsOneWidget); + expect( + find.byKey(const ValueKey('favorites-manage-groups-action')), + findsOneWidget, + ); await estado.crearGrupoFavoritos('Road trip'); await pumpStable(tester); @@ -494,6 +502,32 @@ void main() { }, ); }); + + group('visual fidelity (audit 4.2)', () { + testWidgets('the active group chip is solid brand teal with dark text; ' + 'inactive chips use listSurface (t4:219-221)', (tester) async { + setLargeSurface(tester); + _suppressListTileInkAssertion(); + final estado = await crearEstadoConFavoritos(); + addTearDown(estado.dispose); + + await tester.pumpWidget(buildScreen(estado)); + await pumpStable(tester); + + final activo = tester.widget( + find.widgetWithText(ChoiceChip, 'All · 3'), + ); + expect(activo.selected, isTrue); + expect(activo.selectedColor, const Color(0xFF21D4D9)); + expect(activo.labelStyle?.color, const Color(0xFF062126)); + + final inactivo = tester.widget( + find.widgetWithText(ChoiceChip, 'Rock · 2'), + ); + expect(inactivo.selected, isFalse); + expect(inactivo.backgroundColor, const Color(0xFF102532)); + }); + }); } void setLargeSurface(WidgetTester tester) {