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:
@@ -6,6 +6,7 @@ import '../l10n/display_names.dart';
|
|||||||
import '../l10n/gen/app_localizations.dart';
|
import '../l10n/gen/app_localizations.dart';
|
||||||
import '../modelos/emisora.dart';
|
import '../modelos/emisora.dart';
|
||||||
import '../modelos/grupo_favoritos.dart';
|
import '../modelos/grupo_favoritos.dart';
|
||||||
|
import '../tema/pluriwave_tokens.dart';
|
||||||
import '../widgets/fila_emisora_plana.dart';
|
import '../widgets/fila_emisora_plana.dart';
|
||||||
import '../widgets/pluri_icon.dart';
|
import '../widgets/pluri_icon.dart';
|
||||||
import '../widgets/pluri_layout.dart';
|
import '../widgets/pluri_layout.dart';
|
||||||
@@ -186,22 +187,23 @@ class _PantallaFavoritosState extends State<PantallaFavoritos> {
|
|||||||
children: [
|
children: [
|
||||||
// S1/S2 (Tier 1 visual fidelity): see the empty-state branch
|
// S1/S2 (Tier 1 visual fidelity): see the empty-state branch
|
||||||
// above — PluriScreenHeader is retired everywhere.
|
// 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(
|
PluriRootHeader(
|
||||||
title: l10n.favoritesTitle,
|
title: l10n.favoritesTitle,
|
||||||
onSleepTimer: () => showPluriSleepTimerSheet(context),
|
onSleepTimer: () => showPluriSleepTimerSheet(context),
|
||||||
),
|
actions: [
|
||||||
const SizedBox(height: 12),
|
IconButton(
|
||||||
Row(
|
key: const ValueKey('favorites-manage-groups-action'),
|
||||||
children: [
|
icon: const Icon(Icons.create_new_folder_rounded),
|
||||||
Expanded(
|
tooltip: l10n.favoriteGroupsManage,
|
||||||
child: _FilaChipsGrupos(
|
onPressed: _abrirGestionDeListas,
|
||||||
grupos: gruposVisibles,
|
|
||||||
favoritos: favoritos,
|
|
||||||
seleccionado: seleccionEfectiva,
|
|
||||||
onSeleccionar:
|
|
||||||
(id) => setState(() => _grupoSeleccionadoId = id),
|
|
||||||
onGestionar: _abrirGestionDeListas,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
PopupMenuButton<OrdenEmisoras>(
|
PopupMenuButton<OrdenEmisoras>(
|
||||||
icon: const Icon(Icons.swap_vert_rounded),
|
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.favoritos,
|
||||||
required this.seleccionado,
|
required this.seleccionado,
|
||||||
required this.onSeleccionar,
|
required this.onSeleccionar,
|
||||||
required this.onGestionar,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final List<GrupoFavoritos> grupos;
|
final List<GrupoFavoritos> grupos;
|
||||||
final List<Emisora> favoritos;
|
final List<Emisora> favoritos;
|
||||||
final String? seleccionado;
|
final String? seleccionado;
|
||||||
final ValueChanged<String?> onSeleccionar;
|
final ValueChanged<String?> onSeleccionar;
|
||||||
final VoidCallback onGestionar;
|
|
||||||
|
|
||||||
String _nombreVisible(AppLocalizations l10n, GrupoFavoritos grupo) =>
|
String _nombreVisible(AppLocalizations l10n, GrupoFavoritos grupo) =>
|
||||||
grupo.esSinAsignar ? l10n.favoriteGroupsUnassigned : grupo.nombre;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = AppLocalizations.of(context);
|
final l10n = AppLocalizations.of(context);
|
||||||
@@ -278,38 +314,27 @@ class _FilaChipsGrupos extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 8),
|
padding: const EdgeInsets.only(right: 8),
|
||||||
child: ChoiceChip(
|
child: _chip(
|
||||||
label: Text(
|
label: l10n.favoriteGroupsChipLabel(
|
||||||
l10n.favoriteGroupsChipLabel(
|
l10n.favoritesFilterAllLabel,
|
||||||
l10n.favoritesFilterAllLabel,
|
favoritos.length,
|
||||||
favoritos.length,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
selected: seleccionado == null,
|
selected: seleccionado == null,
|
||||||
onSelected: (_) => onSeleccionar(null),
|
onTap: () => onSeleccionar(null),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
for (final grupo in grupos)
|
for (final grupo in grupos)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 8),
|
padding: const EdgeInsets.only(right: 8),
|
||||||
child: ChoiceChip(
|
child: _chip(
|
||||||
label: Text(
|
label: l10n.favoriteGroupsChipLabel(
|
||||||
l10n.favoriteGroupsChipLabel(
|
_nombreVisible(l10n, grupo),
|
||||||
_nombreVisible(l10n, grupo),
|
favoritos.where((e) => e.grupoFavoritosId == grupo.id).length,
|
||||||
favoritos
|
|
||||||
.where((e) => e.grupoFavoritosId == grupo.id)
|
|
||||||
.length,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
selected: seleccionado == grupo.id,
|
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,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -357,7 +357,12 @@ void main() {
|
|||||||
await tester.pumpWidget(buildScreen(estado));
|
await tester.pumpWidget(buildScreen(estado));
|
||||||
await pumpStable(tester);
|
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);
|
await pumpStable(tester);
|
||||||
|
|
||||||
// "Group Management Reachable from Favoritos": the SAME screen
|
// "Group Management Reachable from Favoritos": the SAME screen
|
||||||
@@ -385,7 +390,10 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.byType(PantallaAjustesGruposFavoritos), findsNothing);
|
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 estado.crearGrupoFavoritos('Road trip');
|
||||||
await pumpStable(tester);
|
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) {
|
void setLargeSurface(WidgetTester tester) {
|
||||||
|
|||||||
Reference in New Issue
Block a user