import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:intl/intl.dart'; import '../services/iptv_provider.dart'; import '../models/xtream_models.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 { int _focusedIndex = 0; @override void initState() { super.initState(); // No automatic data loading on startup } 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 _iconSize => _isLargeScreen ? 80 : 60; 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(); } 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(); } 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(); } void _handleKeyEvent(KeyEvent event) { if (event is KeyDownEvent) { if (event.logicalKey == LogicalKeyboardKey.enter || event.logicalKey == LogicalKeyboardKey.select || event.logicalKey == LogicalKeyboardKey.space) { _handleTap(); } } } @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; final FocusNode _gridFocusNode = FocusNode(); @override void initState() { super.initState(); print('DEBUG: ContentListScreen.initState() - type: ${widget.type}'); _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 _cardTextSize => _isLargeScreen ? 16 : 12; double get _headerPadding => _isLargeScreen ? 32 : 16; void _loadContent() { print('DEBUG: _loadContent() called for type: ${widget.type}'); final provider = context.read(); if (widget.type == ContentType.live) { print('DEBUG: Loading live streams with country filter: "${_selectedCountry ?? ''}"'); 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(); _gridFocusNode.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'; } } List get _categories { final provider = context.read(); switch (widget.type) { case ContentType.live: return provider.liveCategories; case ContentType.movies: return provider.vodCategories; case ContentType.series: return provider.seriesCategories; } } @override Widget build(BuildContext context) { print('DEBUG: ContentListScreen.build() - type: ${widget.type}, isLive: ${widget.type == ContentType.live}'); 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; 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(), 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 = ''); }, ) : 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); }, ), ), ], ), ); } String _getCountryName(String categoryName) { if (categoryName.contains('|')) { return categoryName.split('|').first.trim(); } return categoryName.trim(); } Widget _buildCountrySidebar() { return Consumer( builder: (context, provider, _) { print('🔥 BUILDING SIDEBAR - countries: ${provider.countries.length}, loading: ${provider.isLoading}, organizing: ${provider.isOrganizingCountries}'); return SimpleCountriesSidebar( countries: provider.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), ), ), ], ), ); } List streams = []; if (widget.type == ContentType.live) { streams = provider.filteredLiveStreams; } else if (widget.type == ContentType.movies) { streams = provider.vodStreams; } else { streams = provider.seriesList.map((s) => XtreamStream( streamId: s.seriesId, name: s.name, streamIcon: s.cover, plot: s.plot, rating: s.rating, )).toList(); } if (_searchQuery.isNotEmpty) { // Special case: "arg|" prefix search - exact pattern match for "arg|" in channel name if (_searchQuery.toLowerCase() == 'arg|') { streams = streams .where((s) => s.name.toLowerCase().contains('arg|')) .toList(); } else { // Normal search - contains query anywhere in name streams = streams .where((s) => s.name.toLowerCase().contains(_searchQuery.toLowerCase())) .toList(); } } 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), 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, errorBuilder: (_, __, ___) => _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( bottom: padding, left: padding, right: padding, child: Text( widget.stream.name, style: TextStyle( color: Colors.white, fontSize: textSize, fontWeight: FontWeight.w500, ), maxLines: 2, 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, ), ), ); }, ), ); }, ); }, ), ), ], ), ), ); } }