diff --git a/Dockerfile b/Dockerfile index f360cce..f489207 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,9 +14,10 @@ RUN git config --global --add safe.directory /app RUN flutter config --enable-web # Copy dependency files first for better caching +# Note: pubspec.lock will be generated by flutter pub get if missing COPY pubspec.yaml ./ -# Get dependencies +# Get dependencies (generates pubspec.lock automatically) RUN flutter pub get # Copy source code diff --git a/lib/features/iptv/models/xtream_models.dart b/lib/features/iptv/models/xtream_models.dart index 126a34b..edc5cfd 100644 --- a/lib/features/iptv/models/xtream_models.dart +++ b/lib/features/iptv/models/xtream_models.dart @@ -71,6 +71,7 @@ class Series { final String name; final String? cover; final String? categoryId; + final String categoryName; final String? rating; const Series({ @@ -78,6 +79,7 @@ class Series { required this.name, this.cover, this.categoryId, + this.categoryName = '', this.rating, }); @@ -87,6 +89,7 @@ class Series { name: json['name']?.toString() ?? 'Unknown', cover: json['cover']?.toString(), categoryId: json['category_id']?.toString(), + categoryName: json['category_name']?.toString() ?? '', rating: json['rating']?.toString(), ); } diff --git a/lib/features/iptv/providers/xtream_provider.dart b/lib/features/iptv/providers/xtream_provider.dart index 87711fa..9fbc690 100644 --- a/lib/features/iptv/providers/xtream_provider.dart +++ b/lib/features/iptv/providers/xtream_provider.dart @@ -2,8 +2,16 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/models/playlist_config.dart'; import '../services/xtream_service.dart'; -/// Singleton Xtream service provider -final xtreamServiceProvider = Provider((ref) { +/// Family provider for Xtream service - one instance per playlist +final xtreamServiceProvider = Provider.family((ref, playlist) { + final service = XtreamService(); + service.setPlaylist(playlist); + ref.onDispose(() => service.dispose()); + return service; +}); + +/// Singleton Xtream service provider (legacy, for activeXtreamServiceProvider) +final _singletonXtreamServiceProvider = Provider((ref) { final service = XtreamService(); ref.onDispose(() => service.dispose()); return service; @@ -14,7 +22,7 @@ final selectedPlaylistProvider = StateProvider((ref) => null); /// Provider that watches for playlist changes and updates Xtream service final activeXtreamServiceProvider = Provider((ref) { - final service = ref.watch(xtreamServiceProvider); + final service = ref.watch(_singletonXtreamServiceProvider); final playlist = ref.watch(selectedPlaylistProvider); if (playlist != null) { diff --git a/lib/features/iptv/screens/dashboard_screen.dart b/lib/features/iptv/screens/dashboard_screen.dart index 745414c..a32d88a 100644 --- a/lib/features/iptv/screens/dashboard_screen.dart +++ b/lib/features/iptv/screens/dashboard_screen.dart @@ -28,7 +28,7 @@ class _DashboardScreenState extends ConsumerState { void initState() { super.initState(); _tabs = [ - LiveTVTab(playlist: widget.playlist), + const LiveTVTab(), MoviesTab(playlist: widget.playlist), SeriesTab(playlist: widget.playlist), const SettingsTab(), diff --git a/lib/features/iptv/services/xtream_service.dart b/lib/features/iptv/services/xtream_service.dart index a90d29f..a628af3 100644 --- a/lib/features/iptv/services/xtream_service.dart +++ b/lib/features/iptv/services/xtream_service.dart @@ -3,6 +3,7 @@ import 'package:dio_cache_interceptor/dio_cache_interceptor.dart'; import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_store.dart'; import '../../../core/models/playlist_config.dart'; import '../../../core/models/iptv_models.dart'; +import '../models/xtream_models.dart' as xm; /// Xtream Codes API Service /// @@ -180,6 +181,68 @@ class XtreamService { } } + /// Get movies with pagination support + Future> getMovies({int offset = 0, int limit = 100}) async { + if (_currentPlaylist == null) throw Exception('No playlist configured'); + + try { + final response = await _dio.get( + _currentPlaylist!.apiBaseUrl, + queryParameters: { + 'username': _currentPlaylist!.username, + 'password': _currentPlaylist!.password, + 'action': 'get_vod_streams', + }, + options: Options(extra: _cacheOptions.toExtra()), + ); + + final List allMovies = response.data as List; + + // Apply pagination + final endIndex = (offset + limit) > allMovies.length ? allMovies.length : offset + limit; + if (offset >= allMovies.length) return []; + + final paginatedMovies = allMovies.sublist(offset, endIndex); + + return paginatedMovies + .map((movieData) => xm.Movie.fromJson(movieData as Map)) + .toList(); + } catch (e) { + throw Exception('Failed to fetch movies: $e'); + } + } + + /// Get series with pagination support (returns flat list) + Future> getSeriesPaginated({int offset = 0, int limit = 100}) async { + if (_currentPlaylist == null) throw Exception('No playlist configured'); + + try { + final response = await _dio.get( + _currentPlaylist!.apiBaseUrl, + queryParameters: { + 'username': _currentPlaylist!.username, + 'password': _currentPlaylist!.password, + 'action': 'get_series', + }, + options: Options(extra: _cacheOptions.toExtra()), + ); + + final List allSeries = response.data as List; + + // Apply pagination + final endIndex = (offset + limit) > allSeries.length ? allSeries.length : offset + limit; + if (offset >= allSeries.length) return []; + + final paginatedSeries = allSeries.sublist(offset, endIndex); + + return paginatedSeries + .map((seriesData) => xm.Series.fromJson(seriesData as Map)) + .toList(); + } catch (e) { + throw Exception('Failed to fetch series: $e'); + } + } + /// Get short EPG for a specific stream /// /// Returns "Now" and "Next" program info diff --git a/lib/features/iptv/widgets/movies_tab.dart b/lib/features/iptv/widgets/movies_tab.dart index 87b829f..367c751 100644 --- a/lib/features/iptv/widgets/movies_tab.dart +++ b/lib/features/iptv/widgets/movies_tab.dart @@ -1,3 +1,13 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:google_fonts/google_fonts.dart'; +import '../../../core/models/playlist_config.dart'; +import '../models/xtream_models.dart'; +import '../providers/xtream_provider.dart'; +import '../screens/video_player_screen.dart'; + +class MoviesTab extends ConsumerStatefulWidget { final PlaylistConfig playlist; const MoviesTab({super.key, required this.playlist}); diff --git a/lib/features/iptv/widgets/series_tab.dart b/lib/features/iptv/widgets/series_tab.dart index 95cf6e0..f39f139 100644 --- a/lib/features/iptv/widgets/series_tab.dart +++ b/lib/features/iptv/widgets/series_tab.dart @@ -5,6 +5,7 @@ import 'package:google_fonts/google_fonts.dart'; import '../../../core/models/playlist_config.dart'; import '../services/xtream_service.dart'; import '../models/xtream_models.dart'; +import '../providers/xtream_provider.dart'; class SeriesTab extends ConsumerStatefulWidget { final PlaylistConfig playlist; @@ -52,7 +53,7 @@ class _SeriesTabState extends ConsumerState { try { final service = ref.read(xtreamServiceProvider(widget.playlist)); - final newSeries = await service.getSeries( + final newSeries = await service.getSeriesPaginated( offset: _currentOffset, limit: _pageSize, ); diff --git a/lib/main.dart b/lib/main.dart index d582f3e..dca650c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -28,7 +28,7 @@ class MyApp extends ConsumerWidget { seedColor: Colors.blue, brightness: Brightness.light, ), - cardTheme: CardTheme( + cardTheme: CardThemeData( elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), @@ -41,7 +41,7 @@ class MyApp extends ConsumerWidget { seedColor: Colors.blue, brightness: Brightness.dark, ), - cardTheme: CardTheme( + cardTheme: CardThemeData( elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), diff --git a/pubspec.yaml b/pubspec.yaml index d43f015..54ba9ec 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,6 +30,8 @@ dependencies: video_player: ^2.8.2 video_player_web: ^2.3.0 # Required for web platform chewie: ^1.7.5 + media_kit: ^1.1.10 + media_kit_video: ^1.2.4 # UI Components google_fonts: ^6.1.0