From 7eb483fc07a6ffcce30ac59d8226b6e4bdf59e0b Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Mon, 9 Mar 2026 13:31:01 +0100 Subject: [PATCH] feat: introduce IPTV settings management with a dedicated mobile tab, providers, and service. --- .../iptv/providers/settings_provider.dart | 12 ++++++ .../iptv/providers/xtream_provider.dart | 25 +++++++---- .../iptv/services/xtream_service.dart | 25 +++++++++-- .../iptv/widgets/mobile_settings_tab.dart | 42 ++++++++++++++++++- task.md | 1 + 5 files changed, 92 insertions(+), 13 deletions(-) diff --git a/lib/features/iptv/providers/settings_provider.dart b/lib/features/iptv/providers/settings_provider.dart index d35f15d..4ba6f33 100644 --- a/lib/features/iptv/providers/settings_provider.dart +++ b/lib/features/iptv/providers/settings_provider.dart @@ -65,6 +65,7 @@ class _SettingsKeys { static const String playerType = 'player_type'; // Server/GPU Settings static const String enableNvidiaGpu = 'enable_nvidia_gpu'; + static const String backendUrl = 'backend_url'; } /// Settings state for IPTV preferences with persistence @@ -90,6 +91,7 @@ class IptvSettings { // Server/GPU Settings final bool enableNvidiaGpu; + final String backendUrl; // Manual override const IptvSettings({ // Filters @@ -110,6 +112,7 @@ class IptvSettings { this.playerType = PlayerType.standard, // Server/GPU defaults this.enableNvidiaGpu = false, + this.backendUrl = '', }); IptvSettings copyWith({ @@ -127,6 +130,7 @@ class IptvSettings { String? preferredAspectRatio, PlayerType? playerType, bool? enableNvidiaGpu, + String? backendUrl, }) { return IptvSettings( liveTvCategoryFilter: liveTvCategoryFilter ?? this.liveTvCategoryFilter, @@ -143,6 +147,7 @@ class IptvSettings { preferredAspectRatio: preferredAspectRatio ?? this.preferredAspectRatio, playerType: playerType ?? this.playerType, enableNvidiaGpu: enableNvidiaGpu ?? this.enableNvidiaGpu, + backendUrl: backendUrl ?? this.backendUrl, ); } @@ -344,6 +349,7 @@ class IptvSettingsNotifier extends StateNotifier { .values[remoteSettings[_SettingsKeys.playerType] as int] : null, enableNvidiaGpu: getValue(_SettingsKeys.enableNvidiaGpu), + backendUrl: getValue(_SettingsKeys.backendUrl), ); } _initialized = true; @@ -388,6 +394,7 @@ class IptvSettingsNotifier extends StateNotifier { _SettingsKeys.preferredAspectRatio: state.preferredAspectRatio, _SettingsKeys.playerType: state.playerType.index, _SettingsKeys.enableNvidiaGpu: state.enableNvidiaGpu, + _SettingsKeys.backendUrl: state.backendUrl, }; print('[SettingsProvider] _saveToApi: Saving settings...'); @@ -487,6 +494,11 @@ class IptvSettingsNotifier extends StateNotifier { _saveToApi(); } + void setBackendUrl(String value) { + state = state.copyWith(backendUrl: value); + _saveToApi(); + } + // Legacy methods void setCategoryFilter(String filter) => setLiveTvFilter(filter); void clearCategoryFilter() => clearLiveTvFilter(); diff --git a/lib/features/iptv/providers/xtream_provider.dart b/lib/features/iptv/providers/xtream_provider.dart index 632f38f..0b41e5e 100644 --- a/lib/features/iptv/providers/xtream_provider.dart +++ b/lib/features/iptv/providers/xtream_provider.dart @@ -1,10 +1,14 @@ 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 xtreamServiceProvider = + Provider.family((ref, playlist) { final service = XtreamService(); + final settings = ref.watch(iptvSettingsProvider); + service.setBackendUrl(settings.backendUrl); service.setPlaylist(playlist); ref.onDispose(() => service.dispose()); return service; @@ -13,6 +17,8 @@ final xtreamServiceProvider = Provider.family((re /// Singleton Xtream service provider (legacy, for activeXtreamServiceProvider) final _singletonXtreamServiceProvider = Provider((ref) { final service = XtreamService(); + final settings = ref.watch(iptvSettingsProvider); + service.setBackendUrl(settings.backendUrl); ref.onDispose(() => service.dispose()); return service; }); @@ -24,11 +30,11 @@ final selectedPlaylistProvider = StateProvider((ref) => null); final activeXtreamServiceProvider = Provider((ref) { final service = ref.watch(_singletonXtreamServiceProvider); final playlist = ref.watch(selectedPlaylistProvider); - + if (playlist != null) { service.setPlaylist(playlist); } - + return service; }); @@ -39,7 +45,8 @@ final liveChannelsProvider = FutureProvider.autoDispose((ref) async { }); /// Provider for live TV channels by playlist (family provider) -final liveChannelsByPlaylistProvider = FutureProvider.family.autoDispose((ref, PlaylistConfig playlist) async { +final liveChannelsByPlaylistProvider = + FutureProvider.family.autoDispose((ref, PlaylistConfig playlist) async { final service = ref.watch(xtreamServiceProvider(playlist)); return await service.getLiveChannels(); }); @@ -57,7 +64,8 @@ final seriesProvider = FutureProvider.autoDispose((ref) async { }); /// Provider for EPG of a specific stream -final epgProvider = FutureProvider.family.autoDispose((ref, String streamId) async { +final epgProvider = + FutureProvider.family.autoDispose((ref, String streamId) async { final service = ref.watch(activeXtreamServiceProvider); return await service.getShortEpg(streamId); }); @@ -66,9 +74,9 @@ final epgProvider = FutureProvider.family.autoDispose((ref, String streamId) asy class EpgRequestKey { final PlaylistConfig playlist; final String streamId; - + const EpgRequestKey({required this.playlist, required this.streamId}); - + @override bool operator ==(Object other) => identical(this, other) || @@ -82,7 +90,8 @@ class EpgRequestKey { } /// Provider for EPG with playlist context -final epgByPlaylistProvider = FutureProvider.family.autoDispose((ref, EpgRequestKey key) async { +final epgByPlaylistProvider = + FutureProvider.family.autoDispose((ref, EpgRequestKey key) async { final service = ref.watch(xtreamServiceProvider(key.playlist)); return await service.getShortEpg(key.streamId); }); diff --git a/lib/features/iptv/services/xtream_service.dart b/lib/features/iptv/services/xtream_service.dart index 22974ba..8060240 100644 --- a/lib/features/iptv/services/xtream_service.dart +++ b/lib/features/iptv/services/xtream_service.dart @@ -34,17 +34,34 @@ class XtreamService { _dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions)); } + String? _manualBackendUrl; + /// Get dynamic base URL for the local backend - /// On Web: use localhost (proxy handles it) - /// On Mobile: default to host machine IP for emulator + /// Priority: + /// 1. Manual override from settings + /// 2. Detected origin (Web) + /// 3. Host machine IP (Emulator) String get _backendBaseUrl { - if (kIsWeb) return 'http://localhost:8089'; - // On Android emulator, 10.0.2.2 points to host machine's localhost + if (_manualBackendUrl != null && _manualBackendUrl!.isNotEmpty) { + return _manualBackendUrl!; + } + if (kIsWeb) { + final origin = Uri.base.origin; + if (origin.contains('localhost') && !origin.contains('8089')) { + return 'http://localhost:8089'; + } + return origin; + } return 'http://10.0.2.2:8089'; } String get backendBaseUrl => _backendBaseUrl; + /// Set manual backend URL override + void setBackendUrl(String? url) { + _manualBackendUrl = url; + } + /// Wrap URL with proxy for all external IPTV URLs String _wrapWithProxy(String url) { if (url.startsWith('http')) { diff --git a/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart b/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart index ab23a43..b5dd22c 100644 --- a/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart +++ b/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart @@ -17,7 +17,8 @@ class MobileSettingsTab extends ConsumerStatefulWidget { class _MobileSettingsTabState extends ConsumerState { late TextEditingController _liveTvController; late TextEditingController _moviesController; - late TextEditingController _seriesController; + late final TextEditingController _seriesController; + late final TextEditingController _backendUrlController; bool _initialized = false; @override @@ -26,6 +27,7 @@ class _MobileSettingsTabState extends ConsumerState { _liveTvController = TextEditingController(); _moviesController = TextEditingController(); _seriesController = TextEditingController(); + _backendUrlController = TextEditingController(); } @override @@ -33,6 +35,7 @@ class _MobileSettingsTabState extends ConsumerState { _liveTvController.dispose(); _moviesController.dispose(); _seriesController.dispose(); + _backendUrlController.dispose(); super.dispose(); } @@ -49,6 +52,9 @@ class _MobileSettingsTabState extends ConsumerState { _seriesController.text != settings.seriesCategoryFilter) { _seriesController.text = settings.seriesCategoryFilter; } + if (!_initialized || _backendUrlController.text != settings.backendUrl) { + _backendUrlController.text = settings.backendUrl; + } _initialized = true; } @@ -273,6 +279,40 @@ class _MobileSettingsTabState extends ConsumerState { ), const SizedBox(height: 24), + const _SectionHeader(title: 'Server Settings'), + Container( + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(16), + ), + padding: const EdgeInsets.all(16), + child: Column( + children: [ + _FilterInput( + label: 'Backend URL Override', + icon: Icons.lan, + controller: _backendUrlController, + onChanged: (val) => ref + .read(iptvSettingsProvider.notifier) + .setBackendUrl(val), + ), + if (settings.backendUrl.isEmpty) + Padding( + padding: const EdgeInsets.only(top: 8, left: 32), + child: Text( + 'Currently using: ${Uri.base.origin}', + style: const TextStyle( + fontSize: 10, + color: AppColors.textSecondary, + fontStyle: FontStyle.italic, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 24), + // Content Filters const _SectionHeader(title: 'Content Filters'), Container( diff --git a/task.md b/task.md index 3f18838..084adab 100644 --- a/task.md +++ b/task.md @@ -19,6 +19,7 @@ Refonte du Guide TV pour inclure les catégories et corriger l'EPG. - [x] Correction de l'endpoint EPG Backend (Fallback) - [x] Correction du lecteur Mobile (LitePlayerView) - [x] Alignement UI Mobile (Tri, EPG, URLs) +- [x] Correction Connectivité Mobile (Auto-origin + Manuel Override) ## Progress Log