import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../estado/estado_busqueda.dart'; import '../l10n/gen/app_localizations.dart'; import '../modelos/pais_radio.dart'; import '../tema/pluriwave_tokens.dart'; import '../widgets/pluri_glass_surface.dart'; import '../widgets/pluri_layout.dart'; import '../widgets/pluri_push_scaffold.dart'; /// WU7, `station-discovery-browse` spec — "Países Browser Over the Verified /// Countries Contract". Pure display over `EstadoBusqueda.cargarPaises()` /// (in-memory cache guard, so re-entering this screen never re-fetches): /// a "Tus idiomas" shortlist above the full alphabetical list, both with /// live station counts already parsed from the API's `stationcount` /// **string** field (`PaisRadio.fromApi`, Engram id 2500). class PantallaPaises extends StatefulWidget { const PantallaPaises({super.key, this.onPaisSeleccionado}); /// Item 24 / audit 5.2 (t4:256-269): every row is tappable (the prototype /// draws a `chevron_right` on all of them). Optional so this screen stays /// usable stand-alone; the one production call site /// (`pantalla_buscar.dart`'s "Explorar por" grid) wires this to filter /// search results by the tapped country and pop back. final ValueChanged? onPaisSeleccionado; @override State createState() => _PantallaPaisesState(); } class _PantallaPaisesState extends State { /// One representative country per app-supported locale (the same 13 /// locales as `pantalla_ajustes_idioma.dart`'s `_idiomas` list). "Tus /// idiomas" is named by the proposal/spec but its derivation is not /// otherwise specified — this reuses the app's own existing language /// identity rather than inventing a separate curated country list. static const _paisPorIdioma = { 'en': 'US', 'es': 'ES', 'zh': 'CN', 'hi': 'IN', 'ar': 'SA', 'pt': 'PT', 'fr': 'FR', 'ru': 'RU', 'de': 'DE', 'ja': 'JP', 'id': 'ID', 'bn': 'BD', 'it': 'IT', }; @override void initState() { super.initState(); // `cargarPaises()` calls `notifyListeners()` before its first `await` // (its loading-flag flip) — doing that synchronously inside `initState` // would trigger "setState() or markNeedsBuild() called during build." // Deferred to a post-frame callback, matching the established pattern // for triggering a load from a screen's `initState`/`build` // (`pantalla_alarmas.dart`'s `_favoritosSolicitados` guard). WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) context.read().cargarPaises(); }); } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); final estado = context.watch(); return PluriPushScaffold( title: l10n.countriesScreenTitle, body: estado.cargandoPaises && estado.paises.isEmpty ? const Center(child: CircularProgressIndicator()) : ListView( padding: PluriLayout.pageContentPadding, children: [ _seccionTusIdiomas(context, estado.paises, l10n), const SizedBox(height: 16), _seccionTodos(context, estado.paises, l10n), ], ), ); } void _seleccionar(PaisRadio pais) { widget.onPaisSeleccionado?.call(pais); } Widget _seccionTusIdiomas( BuildContext context, List paises, AppLocalizations l10n, ) { final porCodigo = {for (final p in paises) p.codigoIso: p}; final destacados = _paisPorIdioma.values .map((codigo) => porCodigo[codigo]) .whereType() .toList(); if (destacados.isEmpty) return const SizedBox.shrink(); final theme = Theme.of(context); return PluriGlassSurface( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( l10n.countriesYourLanguagesTitle, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w900, ), ), const SizedBox(height: 8), // Item 24 / audit 5.1 (t4:255-258): a column of tappable ISO // rows, not a Wrap of non-interactive Chips. for (final pais in destacados) _FilaPais( pais: pais, l10n: l10n, // Item 24 / audit 5.5 (t4:256): the first row is highlighted. destacado: pais == destacados.first, onTap: () => _seleccionar(pais), ), ], ), ); } Widget _seccionTodos( BuildContext context, List paises, AppLocalizations l10n, ) { final theme = Theme.of(context); return PluriGlassSurface( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( l10n.countriesAllTitle, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w900, ), ), const SizedBox(height: 4), // Item 24 / audit 5.1-5.3 (t4:262-269): the same tappable ISO row // as "Tus idiomas" -- not the previous ListTile, which had no ISO // column and no onTap. for (final pais in paises) _FilaPais(pais: pais, l10n: l10n, onTap: () => _seleccionar(pais)), ], ), ); } } /// Item 24 / audit 5.1-5.3, 5.5 (t4:256-269): a tappable row -- ISO code /// column, name, station count, and a chevron -- shared by "Tus idiomas" /// and "Todos". [destacado] applies the t4:256 highlight (teal-tinted /// background, bold name) reserved for the very first "Tus idiomas" row. class _FilaPais extends StatelessWidget { const _FilaPais({ required this.pais, required this.l10n, this.destacado = false, this.onTap, }); final PaisRadio pais; final AppLocalizations l10n; final bool destacado; final VoidCallback? onTap; @override Widget build(BuildContext context) { final onSurface = Theme.of(context).colorScheme.onSurface; return DecoratedBox( decoration: BoxDecoration( color: destacado ? PluriWaveTokens.brand.withValues(alpha: 0.1) : Colors.transparent, borderRadius: BorderRadius.circular(14), ), child: Material( type: MaterialType.transparency, child: InkWell( borderRadius: BorderRadius.circular(14), onTap: onTap, child: Padding( padding: const EdgeInsets.all(12), child: Row( children: [ // t4:256-258: 26px/lh1, centred in a 34-wide column. SizedBox( width: 34, child: Text( pais.codigoIso, textAlign: TextAlign.center, style: const TextStyle(fontSize: 26, height: 1), ), ), const SizedBox(width: 14), Expanded( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( pais.nombre, style: TextStyle( fontSize: 15.5, fontWeight: destacado ? FontWeight.w800 : FontWeight.w700, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 2), Text( l10n.stationsCount(pais.numeroEmisoras), style: TextStyle( fontSize: 12, color: onSurface.withValues(alpha: 0.55), ), ), ], ), ), Icon( Icons.chevron_right_rounded, size: 20, color: onSurface.withValues(alpha: 0.4), ), ], ), ), ), ), ); } }