Files
pluriwave/lib/pantallas/ajustes/widgets/fila_ajuste.dart
T
FreeTLab b5b9829faa fix(ajustes): group eyebrow outside the card, per-row accent icons
Audit 10.2 (t4:511): GrupoAjustes's eyebrow now sits OUTSIDE the
PluriGlassSurface, at title-tier (20px) padding -- it used to share
the card's own 16px content padding.

Audit 10.5 (t4:514/526): FilaAjuste gains an optional iconColor.
Wired on the two rows the prototype actually accents: AUDIO's
Ecualizador (brand cyan) and STATIONS' Grupos de favoritos (warmCoral).

Item 10.10 (two extra AUDIO rows -- "Calidad de streaming" and
"Reproduccion sin interrupciones") is NOT implemented: neither concept
has any backing state or service anywhere in this app today. Building
them for real means inventing two new persisted settings, and a
genuine streaming-quality preference would plausibly need to touch
the protected servicio_audio.dart to actually affect playback --
out of scope for a visual-fidelity pass. Decorative rows that open
nothing would be dead UI, which is worse than leaving the gap
disclosed.
2026-07-30 16:05:28 +02:00

138 lines
5.3 KiB
Dart

import 'package:flutter/material.dart';
import '../../../tema/pluriwave_theme.dart';
import '../../../widgets/pluri_glass_surface.dart';
import '../../../widgets/pluri_layout.dart';
/// Design ADR-3: the two nav-row primitives every Settings detail screen is
/// reached through. [GrupoAjustes] is a single [PluriGlassSurface] card
/// carrying a [PluriWaveTypography.eyebrowLabel] group heading and a list of
/// [FilaAjuste] rows, each pushing its detail screen via
/// `PluriPushScaffold.push`. Neither primitive owns any business logic or
/// provider read — they are pure navigation chrome, which is what keeps the
/// Settings root down to "grouped nav rows only".
class GrupoAjustes extends StatelessWidget {
const GrupoAjustes({super.key, required this.titulo, required this.filas});
/// Group heading, styled with [PluriWaveTypography.eyebrowLabel]. Authored
/// already in its display form — this style never applies `toUpperCase()`.
final String titulo;
/// S8 (Tier 1 visual fidelity): `Widget`, not `List<FilaAjuste>` — a few
/// rows source their current-value text asynchronously (e.g. recordings
/// count, app version) and wrap their own `FilaAjuste` in a
/// `FutureBuilder`. `GrupoAjustes` only iterates and inserts dividers; it
/// never reaches into `FilaAjuste`-specific state.
final List<Widget> filas;
@override
Widget build(BuildContext context) {
final type = context.pluriType;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Audit 10.2 (t4 line 511): the group eyebrow sits OUTSIDE the
// card entirely, at title-tier (20px) padding -- it used to live
// INSIDE the PluriGlassSurface, sharing the card's own 16px
// padding.
Padding(
padding: const EdgeInsets.fromLTRB(
PluriLayout.titleHorizontal,
0,
PluriLayout.titleHorizontal,
6,
),
child: Text(titulo, style: type.eyebrowLabel),
),
PluriGlassSurface(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (var i = 0; i < filas.length; i++) ...[
// S10 (Tier 4 visual fidelity): the prototype insets its
// row divider by 47px (t4 line 516), not full-bleed.
if (i > 0) const Divider(height: 1, indent: 47),
filas[i],
],
],
),
),
],
);
}
}
/// A single Settings navigation row: icon, title, an optional trailing
/// current-value string, and a trailing chevron. Tapping it is the row's
/// only behaviour — it carries no switches, sliders or text fields, which
/// is what "zero inline controls" means at the root.
class FilaAjuste extends StatelessWidget {
const FilaAjuste({
super.key,
required this.icon,
required this.titulo,
required this.onTap,
this.valor,
this.iconColor,
});
final IconData icon;
final String titulo;
final VoidCallback onTap;
/// S8 (Tier 1 visual fidelity): the prototype puts a trailing current
/// value on nearly every row (t4 lines 512-539, 625 — e.g. "3 guardados",
/// "Alfabético", "Español"), 13px `rgba(242,247,250,.55)`. Null means
/// "no current value to show" — the row renders exactly as before.
final String? valor;
/// Audit 10.5 (t4 lines 514/516/522 — equalizer `#21D4D9`, hd
/// `#7EE4C2`, folder `#F4B860`): only the first row or two of a group
/// carries an accent colour in the prototype; every other row's icon
/// stays the ambient default. Null (the vast majority of rows) means
/// "no accent" — the icon renders exactly as before.
final Color? iconColor;
@override
Widget build(BuildContext context) {
final type = context.pluriType;
final valorActual = valor;
return ListTile(
contentPadding: EdgeInsets.zero,
// 10.6 (Tier 4 visual fidelity): the prototype's row icon is 21px (t4
// line 514), not Material's 24px default.
leading: Icon(icon, size: 21, color: iconColor),
// 10.8 (Tier 4 visual fidelity): the prototype's row title is
// 14px/w700 (t4 line 514); cardTitle is 14.5/w700 — a one-off
// override, not a new PluriWaveTypography style (mirrors the
// precedent set for the ringing screen's station name, audit 9.7).
title: Text(titulo, style: type.cardTitle.copyWith(fontSize: 14)),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (valorActual != null) ...[
Text(
valorActual,
// bodyStrong is already 13/w600, matching the prototype's row
// value spec exactly — only the colour needs overriding.
style: type.bodyStrong.copyWith(
color: const Color(0xFFF2F7FA).withValues(alpha: 0.55),
),
),
const SizedBox(width: 6),
],
// 10.9 (Tier 4 visual fidelity): the prototype's chevron is 19px
// at 40% opacity (t4 lines 515-539), not Material's 24px
// full-opacity default.
Icon(
Icons.chevron_right_rounded,
size: 19,
color: const Color(0xFFF2F7FA).withValues(alpha: 0.4),
),
],
),
onTap: onTap,
);
}
}