import 'dart:convert'; import 'dart:html' as html; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/models/playlist_config.dart'; import '../services/xtream_service.dart'; import 'settings_provider.dart'; /// Family provider for Xtream service - one instance per playlist final xtreamServiceProvider = Provider.family((ref, playlist) { final service = XtreamService(); final settings = ref.read(iptvSettingsProvider); service.setBackendUrl(settings.backendUrl); service.updateTimeouts(settings.timeoutSeconds); service.setPlaylist(playlist); // Update instance without rebuilding the provider to keep connectivity active ref.listen(iptvSettingsProvider, (previous, next) { service.setBackendUrl(next.backendUrl); service.updateTimeouts(next.timeoutSeconds); }); // ref.onDispose(() => service.dispose()); // Removed to prevent premature Dio destruction return service; }); /// Singleton Xtream service provider (legacy, for activeXtreamServiceProvider) final _singletonXtreamServiceProvider = Provider((ref) { final service = XtreamService(); final settings = ref.read(iptvSettingsProvider); service.setBackendUrl(settings.backendUrl); service.updateTimeouts(settings.timeoutSeconds); // Update instance without rebuilding the provider ref.listen(iptvSettingsProvider, (previous, next) { service.setBackendUrl(next.backendUrl); service.updateTimeouts(next.timeoutSeconds); }); // ref.onDispose(() => service.dispose()); // Removed to prevent premature Dio destruction return service; }); /// Current selected playlist provider with persistence for Web final selectedPlaylistProvider = StateProvider((ref) { // Restore from localStorage if on web try { final stored = html.window.localStorage['selected_playlist']; if (stored != null) { final data = jsonDecode(stored) as Map; return PlaylistConfig.fromJson(data); } } catch (e) { debugPrint('Error restoring playlist: $e'); } return null; }); // Listener to save selected playlist final playlistPersistenceProvider = Provider((ref) { ref.listen(selectedPlaylistProvider, (previous, next) { if (next != null) { html.window.localStorage['selected_playlist'] = jsonEncode(next.toJson()); } else { html.window.localStorage.remove('selected_playlist'); } }); }); /// Provider that watches for playlist changes and updates Xtream service final activeXtreamServiceProvider = Provider((ref) { final service = ref.watch(_singletonXtreamServiceProvider); final playlist = ref.watch(selectedPlaylistProvider); // Ensure persistence listener is active ref.read(playlistPersistenceProvider); if (playlist != null) { service.setPlaylist(playlist); final settings = ref.read(iptvSettingsProvider); service.updateTimeouts(settings.timeoutSeconds); } return service; }); /// Provider for live TV channels grouped by category final liveChannelsProvider = FutureProvider.autoDispose((ref) async { final service = ref.watch(activeXtreamServiceProvider); return await service.getLiveChannels(); }); /// Provider for live TV channels by playlist (family provider) final liveChannelsByPlaylistProvider = FutureProvider.family.autoDispose((ref, PlaylistConfig playlist) async { final service = ref.watch(xtreamServiceProvider(playlist)); return await service.getLiveChannels(); }); /// Provider for VOD items (movies) grouped by category final vodItemsProvider = FutureProvider.autoDispose((ref) async { final service = ref.watch(activeXtreamServiceProvider); return await service.getVodItems(); }); /// Provider for series grouped by category final seriesProvider = FutureProvider.autoDispose((ref) async { final service = ref.watch(activeXtreamServiceProvider); return await service.getSeries(); }); /// Provider for EPG of a specific stream final epgProvider = FutureProvider.family.autoDispose((ref, String streamId) async { final service = ref.watch(activeXtreamServiceProvider); return await service.getShortEpg(streamId); }); /// EPG request key for family provider (combines playlist and streamId) class EpgRequestKey { final PlaylistConfig playlist; final String streamId; const EpgRequestKey({required this.playlist, required this.streamId}); @override bool operator ==(Object other) => identical(this, other) || other is EpgRequestKey && runtimeType == other.runtimeType && playlist == other.playlist && streamId == other.streamId; @override int get hashCode => playlist.hashCode ^ streamId.hashCode; } /// Provider for EPG with playlist context final epgByPlaylistProvider = FutureProvider.family.autoDispose((ref, EpgRequestKey key) async { final service = ref.watch(xtreamServiceProvider(key.playlist)); return await service.getShortEpg(key.streamId); });