import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/models/playlist_config.dart'; import '../../core/models/iptv_models.dart' show Channel; import '../../features/iptv/services/xtream_service_mobile.dart'; import '../../features/iptv/models/xtream_models.dart'; export '../../features/iptv/models/xtream_models.dart'; /// Mobile-specific Xtream service provider import 'package:path_provider/path_provider.dart'; final mobileXtreamServiceProvider = FutureProvider.family( (ref, playlist) async { final dir = await getApplicationDocumentsDirectory(); final service = XtreamServiceMobile(dir.path); await service .setPlaylistAsync(playlist); // Async to resolve DNS before API calls return service; }); /// Mobile-specific live channels provider final mobileLiveChannelsProvider = FutureProvider.family>, PlaylistConfig>( (ref, playlist) async { final service = await ref.watch(mobileXtreamServiceProvider(playlist).future); return service.getLiveChannels(); }); /// Mobile-specific movies pagination provider final mobileMoviesProvider = FutureProvider.family, PlaylistConfig>((ref, playlist) async { final service = await ref.watch(mobileXtreamServiceProvider(playlist).future); return service.getMoviesPaginated(offset: 0, limit: 100); }); /// Mobile-specific series pagination provider final mobileSeriesProvider = FutureProvider.family, PlaylistConfig>((ref, playlist) async { final service = await ref.watch(mobileXtreamServiceProvider(playlist).future); return service.getSeriesPaginated(offset: 0, limit: 100); }); /// Mobile-specific series info provider final mobileSeriesInfoProvider = FutureProvider.family((ref, seriesId) { throw UnimplementedError('Use mobileSeriesInfoByPlaylistProvider instead'); }); /// Series info with playlist context class SeriesInfoRequest { final PlaylistConfig playlist; final String seriesId; SeriesInfoRequest({required this.playlist, required this.seriesId}); @override bool operator ==(Object other) => identical(this, other) || other is SeriesInfoRequest && runtimeType == other.runtimeType && playlist == other.playlist && seriesId == other.seriesId; @override int get hashCode => playlist.hashCode ^ seriesId.hashCode; } final mobileSeriesInfoByPlaylistProvider = FutureProvider.family((ref, request) async { final service = await ref.watch(mobileXtreamServiceProvider(request.playlist).future); return service.getSeriesInfo(request.seriesId); }); // UI State Provider for persistent navigation final mobileLiveTvUiStateProvider = StateProvider((ref) => LiveTvUiState()); class LiveTvUiState { final String? selectedCategory; final bool isCategoryView; LiveTvUiState({this.selectedCategory, this.isCategoryView = true}); LiveTvUiState copyWith({String? selectedCategory, bool? isCategoryView}) { return LiveTvUiState( selectedCategory: selectedCategory ?? this.selectedCategory, isCategoryView: isCategoryView ?? this.isCategoryView, ); } } /// Persistent index for mobile dashboard navigation final mobileDashboardIndexProvider = StateProvider((ref) => 0); /// [P0-1 FIX] Batch EPG provider - Load all EPG data for multiple channels at once /// This prevents the N+1 query problem where each channel card makes its own EPG request class BatchEpgRequest { final PlaylistConfig playlist; final List streamIds; BatchEpgRequest({required this.playlist, required this.streamIds}); @override bool operator ==(Object other) => identical(this, other) || other is BatchEpgRequest && runtimeType == other.runtimeType && playlist == other.playlist && streamIds.toSet() == other.streamIds.toSet(); @override int get hashCode => playlist.hashCode ^ streamIds.toSet().hashCode; } /// Load EPG data for multiple streams in one batch request /// Cache is automatically handled by the service with 1-hour TTL final mobileBatchEpgProvider = FutureProvider.family, BatchEpgRequest>( (ref, request) async { if (request.streamIds.isEmpty) return {}; final service = await ref.watch(mobileXtreamServiceProvider(request.playlist).future); return service.getBatchEPG(request.streamIds); });