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).
This commit is contained in:
2026-07-30 16:22:12 +02:00
parent 94b1e901d1
commit dcd8488874
2 changed files with 97 additions and 38 deletions
+61 -36
View File
@@ -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<PantallaFavoritos> {
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<OrdenEmisoras>(
icon: const Icon(Icons.swap_vert_rounded),
@@ -221,6 +223,13 @@ class _PantallaFavoritosState extends State<PantallaFavoritos> {
),
],
),
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<GrupoFavoritos> grupos;
final List<Emisora> favoritos;
final String? seleccionado;
final ValueChanged<String?> 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,
),
],
),
);
+36 -2
View File
@@ -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<ChoiceChip>(
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<ChoiceChip>(
find.widgetWithText(ChoiceChip, 'Rock · 2'),
);
expect(inactivo.selected, isFalse);
expect(inactivo.backgroundColor, const Color(0xFF102532));
});
});
}
void setLargeSurface(WidgetTester tester) {