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 'player_screen.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @override State createState() => _HomeScreenState(); } class _HomeScreenState extends State { @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(); } void _showLiveCategories() { Navigator.push( context, MaterialPageRoute(builder: (_) => const ContentListScreen(type: ContentType.live)), ); } void _showMovies() { Navigator.push( context, MaterialPageRoute(builder: (_) => const ContentListScreen(type: ContentType.movies)), ); } void _showSeries() { 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) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Row( children: [ Icon(Icons.live_tv, color: Colors.red, size: 32), SizedBox(width: 12), Text( 'XSTREAM TV', style: TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold, letterSpacing: 2, ), ), ], ), Row( children: [ Text(timeStr, style: const TextStyle(color: Colors.white70, fontSize: 16)), const SizedBox(width: 16), Text(dateStr, style: const TextStyle(color: Colors.white54, fontSize: 14)), const SizedBox(width: 24), const Icon(Icons.person, color: Colors.white70, size: 24), const SizedBox(width: 16), IconButton( icon: const Icon(Icons.settings, color: Colors.white70), onPressed: () { context.read().logout(); }, ), ], ), ], ), ); } Widget _buildDashboard() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Row( children: [ Expanded( flex: 2, child: _DashboardCard( title: 'LIVE TV', icon: Icons.tv, gradient: const LinearGradient( colors: [Color(0xFF00c853), Color(0xFF2979ff)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), onTap: _showLiveCategories, ), ), const SizedBox(width: 16), Expanded( child: Column( children: [ Expanded( child: _DashboardCard( title: 'MOVIES', icon: Icons.play_circle_fill, gradient: const LinearGradient( colors: [Color(0xFFff5252), Color(0xFFff9800)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), onTap: _showMovies, ), ), const SizedBox(height: 16), Expanded( child: _DashboardCard( title: 'SERIES', icon: Icons.movie, gradient: const LinearGradient( colors: [Color(0xFF9c27b0), Color(0xFF03a9f4)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), onTap: _showSeries, ), ), ], ), ), ], ), ); } Widget _buildFooter() { return Consumer( builder: (context, provider, _) { final expDate = provider.userInfo?.expDate; final username = provider.userInfo?.username ?? 'Usuario'; return Padding( padding: const EdgeInsets.all(24), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Expiración: ${_formatExpiry(expDate)}', style: const TextStyle(color: Colors.white38, fontSize: 12), ), const Text( 'Términos de Servicio', style: TextStyle(color: Colors.white38, fontSize: 12), ), Text( 'Usuario: $username', style: const TextStyle(color: Colors.white38, fontSize: 12), ), ], ), ); }, ); } } class _DashboardCard extends StatelessWidget { final String title; final IconData icon; final Gradient gradient; final VoidCallback onTap; const _DashboardCard({ required this.title, required this.icon, required this.gradient, required this.onTap, }); @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: Container( decoration: BoxDecoration( gradient: gradient, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.3), blurRadius: 15, offset: const Offset(0, 8), ), ], ), child: Stack( children: [ Positioned( right: -20, bottom: -20, child: Icon( icon, size: 150, color: Colors.white.withValues(alpha: 0.1), ), ), Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 60, color: Colors.white), const SizedBox(height: 12), Text( title, style: const TextStyle( color: Colors.white, fontSize: 24, 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; @override void initState() { super.initState(); _loadContent(); } void _loadContent() { final provider = context.read(); if (widget.type == ContentType.live) { provider.loadLiveStreams(_selectedCountry ?? ''); } else if (widget.type == ContentType.movies) { provider.loadVodStreams(); } else { provider.loadSeries(); } } @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'; } } 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) { return Scaffold( backgroundColor: const Color(0xFF0f0f1a), body: SafeArea( child: Column( children: [ _buildHeader(), _buildCountryFilter(), Expanded(child: _buildContentList()), ], ), ), ); } Widget _buildHeader() { return Container( padding: const EdgeInsets.all(16), child: Row( children: [ IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () => Navigator.pop(context), ), const SizedBox(width: 8), Text( _title, style: const TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold, ), ), const Spacer(), SizedBox( width: 250, height: 44, child: TextField( controller: _searchController, style: const TextStyle(color: Colors.white), decoration: InputDecoration( hintText: 'Buscar...', 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); }, ), ), ], ), ); } Widget _buildCountryFilter() { if (widget.type != ContentType.live) { return const SizedBox.shrink(); } final categories = _categories; if (categories.isEmpty) { return const SizedBox.shrink(); } final countries = categories.map((c) => c.name).toList(); return Container( height: 50, margin: const EdgeInsets.only(bottom: 8), child: ListView.builder( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16), itemCount: countries.length + 1, itemBuilder: (context, index) { if (index == 0) { return Padding( padding: const EdgeInsets.only(right: 8), child: FilterChip( label: const Text('Todos', style: TextStyle(color: Colors.white)), selected: _selectedCountry == null, selectedColor: Colors.red, backgroundColor: Colors.grey[800], onSelected: (_) { setState(() => _selectedCountry = null); context.read().loadLiveStreams(''); }, ), ); } final country = countries[index - 1]; return Padding( padding: const EdgeInsets.only(right: 8), child: FilterChip( label: Text( country.length > 20 ? '${country.substring(0, 20)}...' : country, style: const TextStyle(color: Colors.white), ), selected: _selectedCountry == categories[index - 1].id, selectedColor: Colors.red, backgroundColor: Colors.grey[800], onSelected: (_) { setState(() => _selectedCountry = categories[index - 1].id); context.read().loadLiveStreams(categories[index - 1].id); }, ), ); }, ), ); } Widget _buildContentList() { return Consumer( builder: (context, provider, _) { if (provider.isLoading) { return const Center(child: CircularProgressIndicator(color: Colors.red)); } List streams = []; if (widget.type == ContentType.live) { streams = provider.liveStreams; } 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) { 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: const TextStyle(color: Colors.grey, fontSize: 16), ), ); } return GridView.builder( padding: const EdgeInsets.all(16), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 4, childAspectRatio: 16 / 9, crossAxisSpacing: 12, mainAxisSpacing: 12, ), itemCount: streams.length, itemBuilder: (context, index) { final stream = streams[index]; return _ChannelCard( stream: stream, isSeries: widget.type == ContentType.series, 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 StatelessWidget { final XtreamStream stream; final bool isSeries; final VoidCallback onTap; const _ChannelCard({ required this.stream, required this.isSeries, required this.onTap, }); @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: Container( decoration: BoxDecoration( color: Colors.grey[900], borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.red.withValues(alpha: 0.3)), ), child: Stack( children: [ if (stream.streamIcon != null && stream.streamIcon!.isNotEmpty) ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.network( stream.streamIcon!, width: double.infinity, height: double.infinity, fit: BoxFit.cover, errorBuilder: (_, __, ___) => _buildPlaceholder(), ), ) else _buildPlaceholder(), Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.transparent, Colors.black.withValues(alpha: 0.8), ], ), ), ), Positioned( bottom: 8, left: 8, right: 8, child: Text( stream.name, style: const TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.w500, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), ), if (stream.rating != null) Positioned( top: 8, right: 8, child: Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: Colors.amber, borderRadius: BorderRadius.circular(4), ), child: Text( stream.rating!, style: const TextStyle( color: Colors.black, fontSize: 10, fontWeight: FontWeight.bold, ), ), ), ), ], ), ), ); } Widget _buildPlaceholder() { return Container( color: Colors.grey[800], child: Center( child: Icon( isSeries ? Icons.tv : Icons.play_circle_outline, color: Colors.red, size: 40, ), ), ); } } 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); }); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFF0f0f1a), body: SafeArea( child: Column( children: [ Container( padding: const EdgeInsets.all(16), child: Row( children: [ IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () => Navigator.pop(context), ), const SizedBox(width: 8), Expanded( child: Text( widget.series.name, style: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ), ), Expanded( child: Consumer( builder: (context, provider, _) { if (provider.isLoading) { return const Center( child: CircularProgressIndicator(color: Colors.red), ); } final episodes = provider.seriesEpisodes; if (episodes.isEmpty) { return const Center( child: Text( 'No hay episodios', style: TextStyle(color: Colors.grey), ), ); } return ListView.builder( padding: const EdgeInsets.all(16), itemCount: episodes.length, itemBuilder: (context, index) { final episode = episodes[index]; return Card( color: Colors.grey[900], margin: const EdgeInsets.only(bottom: 8), child: ListTile( leading: const Icon( Icons.play_circle_fill, color: Colors.red, size: 40, ), 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, ), ), ); }, ), ); }, ); }, ), ), ], ), ), ); } }