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` — 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 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; /// Issue 5 (feedback-pruebas): caps how much width the trailing current /// value can claim. `ListTile` gives `trailing` as much width as it wants /// before handing the title whatever is left — an unbounded value (e.g. a /// real, arbitrarily long station name for "Emisora preferida") could /// squeeze the title down to almost nothing, forcing it to wrap across /// several lines that then get cut short by the row's fixed height. static const _anchoMaximoValor = 108.0; @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). // Issue 5: constrained to one line, ellipsizing instead of wrapping — // labels must wrap as little as possible and never render visibly // truncated (a multi-line wrap inside this fixed-height row cuts the // last line short, which reads as broken, not as intentional). title: Text( titulo, style: type.cardTitle.copyWith(fontSize: 14), maxLines: 1, overflow: TextOverflow.ellipsis, ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ if (valorActual != null) ...[ ConstrainedBox( constraints: const BoxConstraints(maxWidth: _anchoMaximoValor), child: 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), ), maxLines: 1, overflow: TextOverflow.ellipsis, textAlign: TextAlign.end, ), ), 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, ); } }