import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:intl/intl.dart'; import '../services/iptv_provider.dart'; import '../models/xtream_models.dart'; import '../utils/channel_name_formatter.dart'; import 'player_screen.dart'; import '../widgets/simple_countries_sidebar.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @override State createState() => _HomeScreenState(); } class _HomeScreenState extends State { @override void initState() { super.initState(); // No automatic data loading on startup } double get _screenWidth => MediaQuery.of(context).size.width; bool get _isLargeScreen => _screenWidth > 900; double get _headerPadding => _isLargeScreen ? 32 : 24; void _showLiveCategories() { Navigator.push( context, MaterialPageRoute( builder: (_) => const ContentListScreen(type: ContentType.live), ), ); } Future _refreshChannels() async { final provider = context.read(); await provider.reloadM3UStreams(); } Future _downloadPlaylistAsJson() async { final provider = context.read(); try { final filePath = await provider.downloadAndSaveM3UAsJson(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Playlist guardada en: $filePath'), duration: const Duration(seconds: 5), action: SnackBarAction(label: 'OK', onPressed: () {}), ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Error: $e'), backgroundColor: Colors.red, duration: const Duration(seconds: 5), ), ); } } } void _showMovies() async { final provider = context.read(); // Cargar películas bajo demanda if (provider.vodStreams.isEmpty) { await provider.loadVodStreams(); } if (!mounted) return; Navigator.push( context, MaterialPageRoute( builder: (_) => const ContentListScreen(type: ContentType.movies), ), ); } void _showSeries() async { final provider = context.read(); // Cargar series bajo demanda if (provider.seriesList.isEmpty) { await provider.loadSeries(); } if (!mounted) return; Navigator.push( context, MaterialPageRoute( builder: (_) => const ContentListScreen(type: ContentType.series), ), ); } String _formatExpiry(int? timestamp) { if (timestamp == null) return 'Ilimitado'; try { final date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000); return DateFormat('dd MMM yyyy').format(date); } catch (_) { return 'Ilimitado'; } } @override Widget build(BuildContext context) { final now = DateTime.now(); final dateStr = DateFormat('EEEE, dd MMMM').format(now); final timeStr = DateFormat('HH:mm').format(now); return Scaffold( body: Container( decoration: const BoxDecoration( gradient: RadialGradient( center: Alignment.center, radius: 1.5, colors: [Color(0xFF1a1a2e), Color(0xFF0f0f1a)], ), ), child: SafeArea( child: Column( children: [ _buildHeader(timeStr, dateStr), Expanded(child: _buildDashboard()), _buildFooter(), ], ), ), ), ); } Widget _buildHeader(String timeStr, String dateStr) { final double titleSize = _isLargeScreen ? 28.0 : 24.0; final double iconSize = _isLargeScreen ? 40.0 : 32.0; return Padding( padding: EdgeInsets.symmetric( horizontal: _headerPadding, vertical: _isLargeScreen ? 24 : 16, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Icon(Icons.live_tv, color: Colors.red, size: iconSize), const SizedBox(width: 12), Text( 'XSTREAM TV', style: TextStyle( color: Colors.white, fontSize: titleSize, fontWeight: FontWeight.bold, letterSpacing: 2, ), ), ], ), Row( children: [ Text( timeStr, style: TextStyle( color: Colors.white70, fontSize: _isLargeScreen ? 20 : 16, ), ), const SizedBox(width: 16), Text( dateStr, style: TextStyle( color: Colors.white54, fontSize: _isLargeScreen ? 16 : 14, ), ), const SizedBox(width: 24), Icon( Icons.person, color: Colors.white70, size: _isLargeScreen ? 32 : 24, ), const SizedBox(width: 16), Consumer( builder: (context, provider, _) { if (provider.isLoading) { return Row( mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: _isLargeScreen ? 32 : 24, height: _isLargeScreen ? 32 : 24, child: CircularProgressIndicator( color: Colors.white70, strokeWidth: 2, ), ), const SizedBox(width: 12), if (provider.totalChannels > 0) Text( 'Cargando canales... ${provider.loadedChannels} de ${provider.totalChannels}', style: TextStyle( color: Colors.white70, fontSize: _isLargeScreen ? 14 : 12, ), ) else Text( 'Cargando canales...', style: TextStyle( color: Colors.white70, fontSize: _isLargeScreen ? 14 : 12, ), ), ], ); } return Focus( child: Builder( builder: (context) { final hasFocus = Focus.of(context).hasFocus; return Container( decoration: hasFocus ? BoxDecoration( shape: BoxShape.circle, border: Border.all( color: Colors.white, width: 2, ), ) : null, child: IconButton( icon: Icon( Icons.refresh, color: hasFocus ? Colors.white : Colors.white70, size: _isLargeScreen ? 32 : 24, ), onPressed: _refreshChannels, tooltip: 'Actualizar canales', ), ); }, ), ); }, ), const SizedBox(width: 8), Focus( child: Builder( builder: (context) { final hasFocus = Focus.of(context).hasFocus; return Container( decoration: hasFocus ? BoxDecoration( shape: BoxShape.circle, border: Border.all(color: Colors.white, width: 2), ) : null, child: IconButton( icon: Icon( Icons.download, color: hasFocus ? Colors.white : Colors.white70, size: _isLargeScreen ? 32 : 24, ), onPressed: () => _downloadPlaylistAsJson(), tooltip: 'Descargar playlist como JSON', ), ); }, ), ), const SizedBox(width: 8), Focus( child: Builder( builder: (context) { final hasFocus = Focus.of(context).hasFocus; return Container( decoration: hasFocus ? BoxDecoration( shape: BoxShape.circle, border: Border.all(color: Colors.white, width: 2), ) : null, child: IconButton( icon: Icon( Icons.settings, color: hasFocus ? Colors.white : Colors.white70, size: _isLargeScreen ? 32 : 24, ), onPressed: () { context.read().logout(); }, ), ); }, ), ), ], ), ], ), ); } Widget _buildDashboard() { final double cardSpacing = _isLargeScreen ? 24.0 : 16.0; return Padding( padding: EdgeInsets.symmetric(horizontal: _headerPadding), child: Row( children: [ Expanded( flex: 2, child: _DashboardCard( title: 'LIVE TV', icon: Icons.tv, isLarge: _isLargeScreen, gradient: const LinearGradient( colors: [Color(0xFF00c853), Color(0xFF2979ff)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), onTap: _showLiveCategories, ), ), SizedBox(width: cardSpacing), Expanded( child: Column( children: [ Expanded( child: _DashboardCard( title: 'MOVIES', icon: Icons.play_circle_fill, isLarge: _isLargeScreen, gradient: const LinearGradient( colors: [Color(0xFFff5252), Color(0xFFff9800)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), onTap: _showMovies, ), ), SizedBox(height: cardSpacing), Expanded( child: _DashboardCard( title: 'SERIES', icon: Icons.tv, isLarge: _isLargeScreen, gradient: const LinearGradient( colors: [Color(0xFF9c27b0), Color(0xFF03a9f4)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), onTap: _showSeries, ), ), ], ), ), ], ), ); } Widget _buildFooter() { final double footerPadding = _isLargeScreen ? 32.0 : 24.0; final double fontSize = _isLargeScreen ? 16.0 : 12.0; return Consumer( builder: (context, provider, _) { final expDate = provider.userInfo?.expDate; final username = provider.userInfo?.username ?? 'Usuario'; return Padding( padding: EdgeInsets.all(footerPadding), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Expiración: ${_formatExpiry(expDate)}', style: TextStyle(color: Colors.white38, fontSize: fontSize), ), Text( 'Términos de Servicio', style: TextStyle(color: Colors.white38, fontSize: fontSize), ), Text( 'Usuario: $username', style: TextStyle(color: Colors.white38, fontSize: fontSize), ), ], ), ); }, ); } } class _DashboardCard extends StatefulWidget { final String title; final IconData icon; final Gradient gradient; final VoidCallback onTap; final bool isLarge; const _DashboardCard({ required this.title, required this.icon, required this.gradient, required this.onTap, this.isLarge = false, }); @override State<_DashboardCard> createState() => _DashboardCardState(); } class _DashboardCardState extends State<_DashboardCard> { bool _hasFocus = false; void _handleTap() { widget.onTap(); } @override Widget build(BuildContext context) { final iconSize = widget.isLarge ? 80.0 : 60.0; final titleSize = widget.isLarge ? 32.0 : 24.0; final bgIconSize = widget.isLarge ? 200.0 : 150.0; return FocusableActionDetector( actions: >{ ActivateIntent: CallbackAction( onInvoke: (intent) { _handleTap(); return null; }, ), ButtonActivateIntent: CallbackAction( onInvoke: (intent) { _handleTap(); return null; }, ), }, onFocusChange: (hasFocus) { setState(() { _hasFocus = hasFocus; }); }, child: GestureDetector( onTap: _handleTap, child: AnimatedContainer( duration: const Duration(milliseconds: 200), decoration: BoxDecoration( gradient: widget.gradient, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: _hasFocus ? Colors.white.withValues(alpha: 0.6) : Colors.black.withValues(alpha: 0.3), blurRadius: _hasFocus ? 35 : 15, spreadRadius: _hasFocus ? 6 : 0, offset: const Offset(0, 8), ), ], border: _hasFocus ? Border.all(color: Colors.white, width: 5) : Border.all(color: Colors.transparent, width: 5), ), child: Stack( children: [ Positioned( right: -40, bottom: -40, child: Icon( widget.icon, size: bgIconSize, color: Colors.white.withValues(alpha: 0.1), ), ), Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(widget.icon, size: iconSize, color: Colors.white), const SizedBox(height: 16), Text( widget.title, style: TextStyle( color: Colors.white, fontSize: titleSize, fontWeight: FontWeight.bold, letterSpacing: 2, ), ), ], ), ), ], ), ), ), ); } } enum ContentType { live, movies, series } class ContentListScreen extends StatefulWidget { final ContentType type; const ContentListScreen({super.key, required this.type}); @override State createState() => _ContentListScreenState(); } class _ContentListScreenState extends State { final TextEditingController _searchController = TextEditingController(); String _searchQuery = ''; String? _selectedCountry; List? _lastSearchSource; String _lastSearchQuery = ''; List? _lastSearchResults; @override void initState() { super.initState(); _loadContent(); } double get _screenWidth => MediaQuery.of(context).size.width; bool get _isLargeScreen => _screenWidth > 900; bool get _isMediumScreen => _screenWidth > 600 && _screenWidth <= 900; int get _gridCrossAxisCount { if (_screenWidth > 900) return 6; if (_screenWidth > 600) return 4; return 3; } double get _titleFontSize => _isLargeScreen ? 32 : (_isMediumScreen ? 28 : 24); double get _headerPadding => _isLargeScreen ? 32 : 16; void _loadContent() { final provider = context.read(); if (widget.type == ContentType.live) { // Keep live list in memory while app is running. if (provider.liveStreams.isEmpty) { provider.loadLiveStreams(_selectedCountry ?? ''); } } else if (widget.type == ContentType.movies) { provider.loadVodStreams(); } else { provider.loadSeries(); } } void _onFootballSelected() { final provider = context.read(); provider.filterByCategory(SpecialCategories.argentineFootball); } @override void dispose() { _searchController.dispose(); super.dispose(); } String get _title { switch (widget.type) { case ContentType.live: return 'TV en Vivo'; case ContentType.movies: return 'Películas'; case ContentType.series: return 'Series'; } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFF0f0f1a), body: SafeArea( child: Column( children: [ _buildHeader(), if (widget.type == ContentType.live) Expanded( child: Row( children: [ _buildCountrySidebar(), Expanded(child: _buildContentList()), ], ), ) else Expanded(child: _buildContentList()), ], ), ), ); } Widget _buildHeader() { final searchWidth = _isLargeScreen ? 350.0 : (_isMediumScreen ? 300.0 : 250.0); final searchHeight = _isLargeScreen ? 56.0 : 44.0; final iconSize = _isLargeScreen ? 32.0 : 24.0; final showSearch = widget.type != ContentType.live; return Container( padding: EdgeInsets.all(_headerPadding), child: Row( children: [ IconButton( icon: Icon(Icons.arrow_back, color: Colors.white, size: iconSize), onPressed: () => Navigator.pop(context), iconSize: 48, padding: EdgeInsets.all(_isLargeScreen ? 12 : 8), ), const SizedBox(width: 8), Text( _title, style: TextStyle( color: Colors.white, fontSize: _titleFontSize, fontWeight: FontWeight.bold, ), ), const Spacer(), if (showSearch) SizedBox( width: searchWidth, height: searchHeight, child: TextField( controller: _searchController, style: TextStyle( color: Colors.white, fontSize: _isLargeScreen ? 18 : 14, ), decoration: InputDecoration( hintText: 'Buscar...', hintStyle: TextStyle( color: Colors.grey, fontSize: _isLargeScreen ? 18 : 14, ), prefixIcon: Icon( Icons.search, color: Colors.grey, size: _isLargeScreen ? 28 : 20, ), suffixIcon: _searchQuery.isNotEmpty ? IconButton( icon: Icon( Icons.clear, color: Colors.grey, size: _isLargeScreen ? 28 : 20, ), onPressed: () { _searchController.clear(); setState(() { _searchQuery = ''; _lastSearchResults = null; }); }, ) : null, filled: true, fillColor: Colors.grey[900], border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, ), contentPadding: EdgeInsets.symmetric( horizontal: 16, vertical: _isLargeScreen ? 16 : 12, ), ), onChanged: (value) { setState(() { _searchQuery = value; }); }, ), ), ], ), ); } List _buildStreamsForType(IPTVProvider provider) { if (widget.type == ContentType.live) { return provider.filteredLiveStreams; } if (widget.type == ContentType.movies) { return provider.vodStreams; } return provider.seriesList .map( (s) => XtreamStream( streamId: s.seriesId, name: s.name, streamIcon: s.cover, plot: s.plot, rating: s.rating, ), ) .toList(growable: false); } List _applySearchFilter(List streams) { if (_searchQuery.isEmpty) { _lastSearchSource = streams; _lastSearchQuery = ''; _lastSearchResults = streams; return streams; } if (identical(streams, _lastSearchSource) && _searchQuery == _lastSearchQuery && _lastSearchResults != null) { return _lastSearchResults!; } final query = _searchQuery.toLowerCase(); final filtered = streams .where((stream) { final name = stream.name.toLowerCase(); if (query == 'arg|') { return name.contains('arg|'); } return name.contains(query); }) .toList(growable: false); _lastSearchSource = streams; _lastSearchQuery = _searchQuery; _lastSearchResults = filtered; return filtered; } Widget _buildCountrySidebar() { return Consumer( builder: (context, provider, _) { final countries = provider.countries; return SimpleCountriesSidebar( countries: countries, selectedCountry: provider.selectedCategory.isNotEmpty ? provider.selectedCategory : provider.selectedCountry, onCountrySelected: (country) => provider.filterByCountry(country), isLoading: provider.isLoading, isOrganizing: provider.isOrganizingCountries, showFootballCategory: true, onFootballSelected: () => _onFootballSelected(), ); }, ); } Widget _buildContentList() { final padding = _isLargeScreen ? 24.0 : 16.0; final spacing = _isLargeScreen ? 16.0 : 12.0; return Consumer( builder: (context, provider, _) { if (provider.isLoading) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator( color: Colors.red, strokeWidth: _isLargeScreen ? 4 : 2, ), const SizedBox(height: 16), if (provider.totalChannels > 0) Text( 'Cargando canales... ${provider.loadedChannels} de ${provider.totalChannels}', style: TextStyle( color: Colors.white70, fontSize: _isLargeScreen ? 18 : 14, ), ) else Text( 'Cargando canales...', style: TextStyle( color: Colors.white70, fontSize: _isLargeScreen ? 18 : 14, ), ), const SizedBox(height: 8), if (provider.totalChannels > 0) SizedBox( width: 200, child: LinearProgressIndicator( value: provider.loadingProgress, backgroundColor: Colors.grey[800], valueColor: const AlwaysStoppedAnimation( Colors.red, ), ), ), ], ), ); } final baseStreams = _buildStreamsForType(provider); final streams = _applySearchFilter(baseStreams); if (streams.isEmpty) { return Center( child: Text( _searchQuery.isNotEmpty ? 'No se encontraron resultados' : 'Sin contenido', style: TextStyle( color: Colors.grey, fontSize: _isLargeScreen ? 20 : 16, ), ), ); } return GridView.builder( padding: EdgeInsets.all(padding), cacheExtent: _isLargeScreen ? 1600 : 1100, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: _gridCrossAxisCount, childAspectRatio: 16 / 9, crossAxisSpacing: spacing, mainAxisSpacing: spacing, ), itemCount: streams.length, itemBuilder: (context, index) { final stream = streams[index]; return _ChannelCard( stream: stream, isSeries: widget.type == ContentType.series, isLarge: _isLargeScreen, onTap: () { if (widget.type == ContentType.series) { final series = provider.seriesList.firstWhere( (s) => s.seriesId == stream.streamId, ); Navigator.push( context, MaterialPageRoute( builder: (_) => SeriesEpisodesScreen(series: series), ), ); } else { Navigator.push( context, MaterialPageRoute( builder: (_) => PlayerScreen( stream: stream, isLive: widget.type == ContentType.live, ), ), ); } }, ); }, ); }, ); } } class _ChannelCard extends StatefulWidget { final XtreamStream stream; final bool isSeries; final VoidCallback onTap; final bool isLarge; const _ChannelCard({ required this.stream, required this.isSeries, required this.onTap, this.isLarge = false, }); @override State<_ChannelCard> createState() => _ChannelCardState(); } class _ChannelCardState extends State<_ChannelCard> { bool _hasFocus = false; void _handleTap() { widget.onTap(); } @override Widget build(BuildContext context) { final textSize = widget.isLarge ? 16.0 : 12.0; final ratingFontSize = widget.isLarge ? 14.0 : 10.0; final placeholderIconSize = widget.isLarge ? 56.0 : 40.0; final padding = widget.isLarge ? 12.0 : 8.0; final ratingPaddingH = widget.isLarge ? 10.0 : 6.0; final ratingPaddingV = widget.isLarge ? 4.0 : 2.0; return FocusableActionDetector( actions: >{ ActivateIntent: CallbackAction( onInvoke: (intent) { _handleTap(); return null; }, ), ButtonActivateIntent: CallbackAction( onInvoke: (intent) { _handleTap(); return null; }, ), }, onFocusChange: (hasFocus) { setState(() { _hasFocus = hasFocus; }); }, child: GestureDetector( onTap: _handleTap, child: AnimatedContainer( duration: const Duration(milliseconds: 200), decoration: BoxDecoration( color: Colors.grey[900], borderRadius: BorderRadius.circular(16), border: Border.all( color: _hasFocus ? Colors.red : Colors.red.withValues(alpha: 0.3), width: _hasFocus ? 3 : 1, ), boxShadow: _hasFocus ? [ BoxShadow( color: Colors.red.withValues(alpha: 0.3), blurRadius: 15, spreadRadius: 2, ), ] : null, ), child: Stack( children: [ if (widget.stream.streamIcon != null && widget.stream.streamIcon!.isNotEmpty) ClipRRect( borderRadius: BorderRadius.circular(16), child: Image.network( widget.stream.streamIcon!, width: double.infinity, height: double.infinity, fit: BoxFit.cover, cacheWidth: widget.isLarge ? 512 : 384, cacheHeight: widget.isLarge ? 288 : 216, filterQuality: FilterQuality.low, gaplessPlayback: false, errorBuilder: (context, error, stackTrace) => _buildPlaceholder(placeholderIconSize), ), ) else _buildPlaceholder(placeholderIconSize), Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.transparent, Colors.black.withValues(alpha: 0.8), ], ), ), ), Positioned( left: padding, right: padding, bottom: padding, child: SizedBox( height: textSize * 2.8, child: Center( child: Text( ChannelNameFormatter.forDisplay(widget.stream.name), style: TextStyle( color: Colors.white, fontSize: textSize, fontWeight: FontWeight.w600, height: 1.15, ), maxLines: 2, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, ), ), ), ), if (widget.stream.rating != null) Positioned( top: padding, right: padding, child: Container( padding: EdgeInsets.symmetric( horizontal: ratingPaddingH, vertical: ratingPaddingV, ), decoration: BoxDecoration( color: Colors.amber, borderRadius: BorderRadius.circular(6), ), child: Text( widget.stream.rating!, style: TextStyle( color: Colors.black, fontSize: ratingFontSize, fontWeight: FontWeight.bold, ), ), ), ), ], ), ), ), ); } Widget _buildPlaceholder(double iconSize) { return Container( color: Colors.grey[800], child: Center( child: Icon( widget.isSeries ? Icons.tv : Icons.play_circle_outline, color: Colors.red, size: iconSize, ), ), ); } } class SeriesEpisodesScreen extends StatefulWidget { final XtreamSeries series; const SeriesEpisodesScreen({super.key, required this.series}); @override State createState() => _SeriesEpisodesScreenState(); } class _SeriesEpisodesScreenState extends State { @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { context.read().loadSeriesEpisodes(widget.series); }); } double get _screenWidth => MediaQuery.of(context).size.width; bool get _isLargeScreen => _screenWidth > 900; @override Widget build(BuildContext context) { final double fontSize = _isLargeScreen ? 24.0 : 20.0; final double iconSize = _isLargeScreen ? 56.0 : 40.0; final double padding = _isLargeScreen ? 24.0 : 16.0; return Scaffold( backgroundColor: const Color(0xFF0f0f1a), body: SafeArea( child: Column( children: [ Container( padding: EdgeInsets.all(padding), child: Row( children: [ IconButton( icon: Icon( Icons.arrow_back, color: Colors.white, size: iconSize, ), onPressed: () => Navigator.pop(context), iconSize: 48, padding: EdgeInsets.all(_isLargeScreen ? 12 : 8), ), const SizedBox(width: 8), Expanded( child: Text( widget.series.name, style: TextStyle( color: Colors.white, fontSize: fontSize, fontWeight: FontWeight.bold, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ), ), Expanded( child: Consumer( builder: (context, provider, _) { if (provider.isLoading) { return Center( child: CircularProgressIndicator( color: Colors.red, strokeWidth: _isLargeScreen ? 4 : 2, ), ); } final episodes = provider.seriesEpisodes; if (episodes.isEmpty) { return Center( child: Text( 'No hay episodios', style: TextStyle( color: Colors.grey, fontSize: _isLargeScreen ? 20 : 16, ), ), ); } return ListView.builder( padding: EdgeInsets.all(padding), itemCount: episodes.length, itemBuilder: (context, index) { final episode = episodes[index]; return Card( color: Colors.grey[900], margin: EdgeInsets.only( bottom: _isLargeScreen ? 16 : 8, ), child: ListTile( contentPadding: EdgeInsets.all( _isLargeScreen ? 16 : 12, ), leading: Icon( Icons.play_circle_fill, color: Colors.red, size: iconSize, ), title: Text( 'S${episode.seasonNumber}E${episode.episodeNumber} - ${episode.title}', style: TextStyle( color: Colors.white, fontSize: _isLargeScreen ? 20 : 16, ), ), onTap: () { Navigator.push( context, MaterialPageRoute( builder: (_) => PlayerScreen( stream: XtreamStream( streamId: episode.episodeId, name: episode.title, containerExtension: episode.containerExtension, url: episode.url, ), isLive: false, ), ), ); }, ), ); }, ); }, ), ), ], ), ), ); } }