diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index cb2e9bc..4620711 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -108,7 +108,21 @@ class _HomeScreenState extends State { } } -class _LiveTab extends StatelessWidget { +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( @@ -117,18 +131,70 @@ class _LiveTab extends StatelessWidget { 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: [ - _CategoryDropdown( - categories: provider.liveCategories, - selectedCategory: provider.selectedLiveCategory, - onCategorySelected: (categoryId) { - provider.loadLiveStreams(categoryId); - }, + 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: provider.liveStreams, + streams: filteredStreams, + searchQuery: _searchQuery, onStreamSelected: (stream) { Navigator.push( context, @@ -146,7 +212,21 @@ class _LiveTab extends StatelessWidget { } } -class _MoviesTab extends StatelessWidget { +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( @@ -155,23 +235,75 @@ class _MoviesTab extends StatelessWidget { 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: [ - _CategoryDropdown( - categories: provider.vodCategories, - selectedCategory: provider.selectedVodCategory, - onCategorySelected: (categoryId) { - provider.loadVodStreams(categoryId); - }, + 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: provider.vodStreams, + streams: filteredStreams, + searchQuery: _searchQuery, onStreamSelected: (stream) { Navigator.push( context, MaterialPageRoute( - builder: (_) => PlayerScreen(stream: stream), + builder: (_) => PlayerScreen(stream: stream, isLive: false), ), ); }, @@ -184,7 +316,21 @@ class _MoviesTab extends StatelessWidget { } } -class _SeriesTab extends StatelessWidget { +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( @@ -197,21 +343,71 @@ class _SeriesTab extends StatelessWidget { return _SeriesDetailScreen(series: provider.selectedSeries!); } - return _StreamList( - streams: provider.seriesList.map((s) => XtreamStream( - streamId: s.seriesId, - name: s.name, - streamIcon: s.cover, - plot: s.plot, - rating: s.rating, - )).toList(), - isSeries: true, - onStreamSelected: (stream) { - final series = provider.seriesList.firstWhere( - (s) => s.seriesId == stream.streamId, - ); - provider.loadSeriesEpisodes(series); - }, + 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); + }, + ), + ), + ], ); }, ); @@ -335,20 +531,22 @@ 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 const Center( + return Center( child: Text( - 'No content available', - style: TextStyle(color: Colors.grey), + searchQuery.isNotEmpty ? 'No se encontraron canales' : 'No content available', + style: const TextStyle(color: Colors.grey), ), ); } @@ -358,6 +556,7 @@ class _StreamList extends StatelessWidget { itemBuilder: (context, index) { final stream = streams[index]; return ListTile( + dense: true, leading: isSeries && stream.streamIcon != null ? ClipRRect( borderRadius: BorderRadius.circular(4), @@ -373,17 +572,12 @@ class _StreamList extends StatelessWidget { ), ), ) - : Icon( - isSeries ? Icons.tv : Icons.play_circle_outline, + : const Icon( + Icons.play_circle_outline, color: Colors.red, size: 40, ), - title: Text( - stream.name, - style: const TextStyle(color: Colors.white), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), + title: _buildTitle(stream.name), subtitle: stream.rating != null ? Row( children: [ @@ -400,4 +594,48 @@ class _StreamList extends StatelessWidget { }, ); } + + 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)), + ], + ), + ); + } }