import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:provider/provider.dart'; import 'package:shimmer/shimmer.dart' as shimmer; import '../estado/estado_radio.dart'; import '../l10n/app_localizations_ext.dart'; import '../l10n/gen/app_localizations.dart'; import '../widgets/pluri_glass_surface.dart'; import '../widgets/pluri_icon.dart'; import '../widgets/pluri_layout.dart'; import '../widgets/pluri_premium_widgets.dart'; import 'package:pluriwave/widgets/tarjeta_emisora.dart'; import 'reproducir_minimizado.dart'; /// Pantalla principal: emisoras populares y por género. class PantallaInicio extends StatefulWidget { const PantallaInicio({super.key}); @override State createState() => _PantallaInicioState(); } class _PantallaInicioState extends State { static const _generos = [ 'pop', 'rock', 'jazz', 'classical', 'electronic', 'news', 'talk', 'hip-hop', 'country', 'metal', 'reggae', 'latin', ]; String? _generoSeleccionado; @override Widget build(BuildContext context) { final estado = context.watch(); final theme = Theme.of(context); final l10n = AppLocalizations.of(context); return RefreshIndicator( onRefresh: estado.cargarPopulares, child: CustomScrollView( slivers: [ SliverToBoxAdapter(child: _heroHeader(context, estado, l10n)), SliverToBoxAdapter(child: _seccionCercanas(estado, theme, l10n)), SliverToBoxAdapter(child: _seccionTendencias(estado, theme, l10n)), SliverToBoxAdapter(child: _chipGeneros(context, theme, l10n)), if (estado.error != null) SliverToBoxAdapter(child: _errorBanner(estado, theme, l10n)), SliverPadding( padding: const EdgeInsets.fromLTRB(PluriLayout.horizontal, 0, PluriLayout.horizontal, PluriLayout.bottomChromeInset), sliver: _gridEmisoras(estado, l10n), ), ], ), ); } Widget _heroHeader( BuildContext context, EstadoRadio estado, AppLocalizations l10n, ) { return PluriScreenHeader( title: l10n.appTitle, subtitle: l10n.homeScreenSubtitle, glyph: PluriIconGlyph.home, primaryActionLabel: l10n.exploreStations, onPrimaryAction: estado.cargarPopulares, trailing: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ PluriStatusPill( icon: Icons.public_rounded, label: l10n.homeStationsCount(estado.emisorasInicio.length), accent: Theme.of(context).colorScheme.secondary, ), const SizedBox(height: 8), PluriStatusPill( icon: Icons.hd_rounded, label: l10n.qualityHd, ), ], ), ); } Widget _seccionCercanas( EstadoRadio estado, ThemeData theme, AppLocalizations l10n, ) { final pais = estado.paisCercanoDetectado; return Padding( padding: const EdgeInsets.fromLTRB(PluriLayout.horizontal, 8, PluriLayout.horizontal, 0), child: PluriGlassSurface( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( pais == null ? l10n.nearYou : l10n.nearYouInCountry(pais), style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w900, ), ), ), TextButton.icon( onPressed: estado.cargandoCercanas ? null : estado.cargarEmisorasCercanas, icon: estado.cargandoCercanas ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.my_location_rounded, size: 18), label: Text(l10n.detectAction), ), ], ), if (estado.errorCercanas != null) Text( estado.errorCercanas!, style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.error, ), ), if (estado.emisorasCercanas.isNotEmpty) ...[ const SizedBox(height: 8), SizedBox( height: 76, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: estado.emisorasCercanas.length, separatorBuilder: (_, __) => const SizedBox(width: 8), itemBuilder: (context, i) { final emisora = estado.emisorasCercanas[i]; return SizedBox( width: 260, child: TarjetaEmisora( emisora: emisora, esCompacta: true, onTap: () => reproducirMinimizado(context, emisora), ), ); }, ), ), ], ], ), ), ); } Widget _seccionTendencias( EstadoRadio estado, ThemeData theme, AppLocalizations l10n, ) { return Padding( padding: const EdgeInsets.fromLTRB(PluriLayout.horizontal, 8, PluriLayout.horizontal, 0), child: PluriGlassSurface( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(l10n.liveRadar, style: theme.textTheme.titleMedium), const SizedBox(height: 8), SizedBox( height: 56, child: estado.cargandoPopulares ? ListView.separated( scrollDirection: Axis.horizontal, itemCount: 5, separatorBuilder: (_, __) => const SizedBox(width: 8), itemBuilder: (_, __) => _ChipShimmer(theme: theme), ) : ListView.separated( scrollDirection: Axis.horizontal, itemCount: estado.tendencias.length, separatorBuilder: (_, __) => const SizedBox(width: 8), itemBuilder: (context, i) { final e = estado.tendencias[i]; return ActionChip( avatar: const Icon( Icons.graphic_eq_rounded, size: 18, ), label: Text(e.nombre, maxLines: 1), onPressed: () => reproducirMinimizado(context, e), ).animate().fadeIn(delay: (i * 50).ms); }, ), ), ], ), ), ); } Widget _chipGeneros( BuildContext context, ThemeData theme, AppLocalizations l10n, ) { return Padding( padding: const EdgeInsets.fromLTRB(PluriLayout.horizontal, 16, PluriLayout.horizontal, 8), child: PluriGlassSurface( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(l10n.genresTitle, style: theme.textTheme.titleMedium), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 4, children: _generos.map((g) { final seleccionado = _generoSeleccionado == g; return FilterChip( label: Text(l10n.genreName(g)), selected: seleccionado, onSelected: (_) { setState(() { _generoSeleccionado = seleccionado ? null : g; }); if (!seleccionado) { context.read().buscar(tag: g); } else { context.read().cargarPopulares(); } }, ); }).toList(), ), ], ), ), ); } Widget _errorBanner( EstadoRadio estado, ThemeData theme, AppLocalizations l10n, ) { return Padding( padding: const EdgeInsets.all(16), child: PluriGlassSurface( padding: const EdgeInsets.all(12), child: Row( children: [ Icon(Icons.wifi_off, color: theme.colorScheme.error), const SizedBox(width: 8), Expanded(child: Text(estado.error!)), TextButton( onPressed: estado.cargarPopulares, child: Text(l10n.retryAction), ), ], ), ), ); } Widget _gridEmisoras(EstadoRadio estado, AppLocalizations l10n) { final emisoras = _generoSeleccionado != null ? estado.resultadosBusqueda : estado.emisorasInicio; final cargando = estado.cargandoPopulares || (_generoSeleccionado != null && estado.cargandoBusqueda); if (cargando) { return SliverGrid( delegate: SliverChildBuilderDelegate( (_, __) => const TarjetaEmisoraShimmer(), childCount: 12, ), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, childAspectRatio: 0.78, crossAxisSpacing: 12, mainAxisSpacing: 12, ), ); } if (emisoras.isEmpty) { return SliverFillRemaining( child: PluriEmptyState( glyph: PluriIconGlyph.home, title: l10n.noStationsAvailable, subtitle: l10n.noStationsAvailableSubtitle, ), ); } return SliverGrid( delegate: SliverChildBuilderDelegate( (context, i) => TarjetaEmisora( emisora: emisoras[i], onTap: () => reproducirMinimizado(context, emisoras[i]), ).animate().fadeIn(delay: (i * 30).ms).slideY(begin: 0.1), childCount: emisoras.length, ), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, childAspectRatio: 0.78, crossAxisSpacing: 12, mainAxisSpacing: 12, ), ); } } class _ChipShimmer extends StatelessWidget { final ThemeData theme; const _ChipShimmer({required this.theme}); @override Widget build(BuildContext context) { return shimmer.Shimmer.fromColors( baseColor: theme.colorScheme.surfaceContainerHighest, highlightColor: theme.colorScheme.surface, child: Container( width: 120, height: 56, decoration: BoxDecoration( color: theme.colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(20), ), ), ); } }