import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../services/iptv_provider.dart'; import '../models/xtream_models.dart'; import 'player_screen.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @override State createState() => _HomeScreenState(); } class _HomeScreenState extends State { int _selectedTab = 0; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _loadInitialData(); }); } Future _loadInitialData() async { final provider = context.read(); await provider.loadLiveStreams(); await provider.loadVodStreams(); await provider.loadSeries(); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.black, appBar: AppBar( backgroundColor: Colors.red, title: Consumer( builder: (context, provider, _) { return Text( provider.userInfo != null ? 'XStream TV - ${provider.userInfo!.username}' : 'XStream TV', ); }, ), actions: [ IconButton( icon: const Icon(Icons.refresh), onPressed: _loadInitialData, ), IconButton( icon: const Icon(Icons.logout), onPressed: () { context.read().logout(); }, ), ], ), body: Row( children: [ NavigationRail( selectedIndex: _selectedTab, onDestinationSelected: (index) { setState(() => _selectedTab = index); }, backgroundColor: Colors.grey[900], selectedIconTheme: const IconThemeData(color: Colors.red), unselectedIconTheme: const IconThemeData(color: Colors.grey), labelType: NavigationRailLabelType.all, destinations: const [ NavigationRailDestination( icon: Icon(Icons.live_tv), selectedIcon: Icon(Icons.live_tv, color: Colors.red), label: Text('Live', style: TextStyle(color: Colors.white)), ), NavigationRailDestination( icon: Icon(Icons.movie), selectedIcon: Icon(Icons.movie, color: Colors.red), label: Text('Movies', style: TextStyle(color: Colors.white)), ), NavigationRailDestination( icon: Icon(Icons.tv), selectedIcon: Icon(Icons.tv, color: Colors.red), label: Text('Series', style: TextStyle(color: Colors.white)), ), ], ), Expanded( child: _buildContent(), ), ], ), ); } Widget _buildContent() { switch (_selectedTab) { case 0: return _LiveTab(); case 1: return _MoviesTab(); case 2: return _SeriesTab(); default: return _LiveTab(); } } } class _LiveTab extends StatefulWidget { @override State<_LiveTab> createState() => _LiveTabState(); } class _LiveTabState extends State<_LiveTab> { final TextEditingController _searchController = TextEditingController(); String _searchQuery = ''; @override void dispose() { _searchController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Consumer( builder: (context, provider, _) { if (provider.isLoading) { return const Center(child: CircularProgressIndicator(color: Colors.red)); } List filteredStreams = provider.liveStreams; if (_searchQuery.isNotEmpty) { filteredStreams = provider.liveStreams .where((s) => s.name.toLowerCase().contains(_searchQuery.toLowerCase())) .toList(); } return Column( children: [ Padding( padding: const EdgeInsets.all(8), child: Row( children: [ Expanded( child: _CategoryDropdown( categories: provider.liveCategories, selectedCategory: provider.selectedLiveCategory, onCategorySelected: (categoryId) { provider.loadLiveStreams(categoryId); _searchController.clear(); setState(() => _searchQuery = ''); }, ), ), const SizedBox(width: 8), SizedBox( width: 250, height: 48, child: TextField( controller: _searchController, style: const TextStyle(color: Colors.white), decoration: InputDecoration( hintText: 'Buscar canal...', hintStyle: const TextStyle(color: Colors.grey), prefixIcon: const Icon(Icons.search, color: Colors.grey), suffixIcon: _searchQuery.isNotEmpty ? IconButton( icon: const Icon(Icons.clear, color: Colors.grey), onPressed: () { _searchController.clear(); setState(() => _searchQuery = ''); }, ) : null, filled: true, fillColor: Colors.grey[900], border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.symmetric(horizontal: 16), ), onChanged: (value) { setState(() => _searchQuery = value); }, ), ), ], ), ), Expanded( child: _StreamList( streams: filteredStreams, searchQuery: _searchQuery, onStreamSelected: (stream) { Navigator.push( context, MaterialPageRoute( builder: (_) => PlayerScreen(stream: stream), ), ); }, ), ), ], ); }, ); } } class _MoviesTab extends StatefulWidget { @override State<_MoviesTab> createState() => _MoviesTabState(); } class _MoviesTabState extends State<_MoviesTab> { final TextEditingController _searchController = TextEditingController(); String _searchQuery = ''; @override void dispose() { _searchController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Consumer( builder: (context, provider, _) { if (provider.isLoading) { return const Center(child: CircularProgressIndicator(color: Colors.red)); } List filteredStreams = provider.vodStreams; if (_searchQuery.isNotEmpty) { filteredStreams = provider.vodStreams .where((s) => s.name.toLowerCase().contains(_searchQuery.toLowerCase())) .toList(); } return Column( children: [ Padding( padding: const EdgeInsets.all(8), child: Row( children: [ Expanded( child: _CategoryDropdown( categories: provider.vodCategories, selectedCategory: provider.selectedVodCategory, onCategorySelected: (categoryId) { provider.loadVodStreams(categoryId); _searchController.clear(); setState(() => _searchQuery = ''); }, ), ), const SizedBox(width: 8), SizedBox( width: 250, height: 48, child: TextField( controller: _searchController, style: const TextStyle(color: Colors.white), decoration: InputDecoration( hintText: 'Buscar pelĂ­cula...', hintStyle: const TextStyle(color: Colors.grey), prefixIcon: const Icon(Icons.search, color: Colors.grey), suffixIcon: _searchQuery.isNotEmpty ? IconButton( icon: const Icon(Icons.clear, color: Colors.grey), onPressed: () { _searchController.clear(); setState(() => _searchQuery = ''); }, ) : null, filled: true, fillColor: Colors.grey[900], border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.symmetric(horizontal: 16), ), onChanged: (value) { setState(() => _searchQuery = value); }, ), ), ], ), ), Expanded( child: _StreamList( streams: filteredStreams, searchQuery: _searchQuery, onStreamSelected: (stream) { Navigator.push( context, MaterialPageRoute( builder: (_) => PlayerScreen(stream: stream, isLive: false), ), ); }, ), ), ], ); }, ); } } class _SeriesTab extends StatefulWidget { @override State<_SeriesTab> createState() => _SeriesTabState(); } class _SeriesTabState extends State<_SeriesTab> { final TextEditingController _searchController = TextEditingController(); String _searchQuery = ''; @override void dispose() { _searchController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Consumer( builder: (context, provider, _) { if (provider.isLoading) { return const Center(child: CircularProgressIndicator(color: Colors.red)); } if (provider.selectedSeries != null) { return _SeriesDetailScreen(series: provider.selectedSeries!); } List filteredSeries = provider.seriesList.map((s) => XtreamStream( streamId: s.seriesId, name: s.name, streamIcon: s.cover, plot: s.plot, rating: s.rating, )).toList(); if (_searchQuery.isNotEmpty) { filteredSeries = filteredSeries .where((s) => s.name.toLowerCase().contains(_searchQuery.toLowerCase())) .toList(); } return Column( children: [ Padding( padding: const EdgeInsets.all(8), child: SizedBox( width: 300, height: 48, child: TextField( controller: _searchController, style: const TextStyle(color: Colors.white), decoration: InputDecoration( hintText: 'Buscar serie...', hintStyle: const TextStyle(color: Colors.grey), prefixIcon: const Icon(Icons.search, color: Colors.grey), suffixIcon: _searchQuery.isNotEmpty ? IconButton( icon: const Icon(Icons.clear, color: Colors.grey), onPressed: () { _searchController.clear(); setState(() => _searchQuery = ''); }, ) : null, filled: true, fillColor: Colors.grey[900], border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.symmetric(horizontal: 16), ), onChanged: (value) { setState(() => _searchQuery = value); }, ), ), ), Expanded( child: _StreamList( streams: filteredSeries, searchQuery: _searchQuery, isSeries: true, onStreamSelected: (stream) { final series = provider.seriesList.firstWhere( (s) => s.seriesId == stream.streamId, ); provider.loadSeriesEpisodes(series); }, ), ), ], ); }, ); } } class _SeriesDetailScreen extends StatelessWidget { final XtreamSeries series; const _SeriesDetailScreen({required this.series}); @override Widget build(BuildContext context) { return Consumer( builder: (context, provider, _) { final episodes = provider.seriesEpisodes; return Column( children: [ Padding( padding: const EdgeInsets.all(16), child: Row( children: [ IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () { provider.loadSeries(); }, ), Expanded( child: Text( series.name, style: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, ), ), ), ], ), ), Expanded( child: ListView.builder( itemCount: episodes.length, itemBuilder: (context, index) { final episode = episodes[index]; return ListTile( leading: const Icon(Icons.play_circle_outline, color: Colors.red), title: Text( 'S${episode.seasonNumber}E${episode.episodeNumber} - ${episode.title}', style: const TextStyle(color: Colors.white), ), onTap: () { Navigator.push( context, MaterialPageRoute( builder: (_) => PlayerScreen( stream: XtreamStream( streamId: episode.episodeId, name: episode.title, containerExtension: episode.containerExtension, url: episode.url, ), isLive: false, ), ), ); }, ); }, ), ), ], ); }, ); } } class _CategoryDropdown extends StatelessWidget { final List categories; final String selectedCategory; final Function(String) onCategorySelected; const _CategoryDropdown({ required this.categories, required this.selectedCategory, required this.onCategorySelected, }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), color: Colors.grey[900], child: DropdownButton( value: selectedCategory.isEmpty ? null : selectedCategory, hint: const Text('All Categories', style: TextStyle(color: Colors.white)), dropdownColor: Colors.grey[800], isExpanded: true, items: [ const DropdownMenuItem( value: '', child: Text('All Categories', style: TextStyle(color: Colors.white)), ), ...categories.map((c) => DropdownMenuItem( value: c.id, child: Text(c.name, style: const TextStyle(color: Colors.white)), )), ], onChanged: (value) { onCategorySelected(value ?? ''); }, ), ); } } class _StreamList extends StatelessWidget { final List streams; final Function(XtreamStream) onStreamSelected; final bool isSeries; final String searchQuery; const _StreamList({ required this.streams, required this.onStreamSelected, this.isSeries = false, this.searchQuery = '', }); @override Widget build(BuildContext context) { if (streams.isEmpty) { return Center( child: Text( searchQuery.isNotEmpty ? 'No se encontraron canales' : 'No content available', style: const TextStyle(color: Colors.grey), ), ); } return ListView.builder( itemCount: streams.length, itemBuilder: (context, index) { final stream = streams[index]; return ListTile( dense: true, leading: isSeries && stream.streamIcon != null ? ClipRRect( borderRadius: BorderRadius.circular(4), child: Image.network( stream.streamIcon!, width: 40, height: 60, fit: BoxFit.cover, errorBuilder: (_, __, ___) => const Icon( Icons.tv, color: Colors.red, size: 40, ), ), ) : const Icon( Icons.play_circle_outline, color: Colors.red, size: 40, ), title: _buildTitle(stream.name), subtitle: stream.rating != null ? Row( children: [ const Icon(Icons.star, color: Colors.amber, size: 14), Text( stream.rating ?? '', style: const TextStyle(color: Colors.amber), ), ], ) : null, onTap: () => onStreamSelected(stream), ); }, ); } Widget _buildTitle(String name) { if (searchQuery.isEmpty) { return Text( name, style: const TextStyle(color: Colors.white), maxLines: 2, overflow: TextOverflow.ellipsis, ); } final lowerName = name.toLowerCase(); final lowerQuery = searchQuery.toLowerCase(); final startIndex = lowerName.indexOf(lowerQuery); if (startIndex == -1) { return Text( name, style: const TextStyle(color: Colors.white), maxLines: 2, overflow: TextOverflow.ellipsis, ); } return RichText( maxLines: 2, overflow: TextOverflow.ellipsis, text: TextSpan( style: const TextStyle(color: Colors.white), children: [ TextSpan(text: name.substring(0, startIndex)), TextSpan( text: name.substring(startIndex, startIndex + searchQuery.length), style: const TextStyle( backgroundColor: Colors.yellow, color: Colors.black, fontWeight: FontWeight.bold, ), ), TextSpan(text: name.substring(startIndex + searchQuery.length)), ], ), ); } }