feat: introduce IPTV settings management with a dedicated mobile tab, providers, and service.

This commit is contained in:
Michael committed 2026-03-09 13:31:01 +01:00
1 parent 7ea12511a0
commit 7eb483fc07
5 files changed
+92 -13

No files matched your search

@@ -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<IptvSettings> {
.values[remoteSettings[_SettingsKeys.playerType] as int]
: null,
enableNvidiaGpu: getValue<bool>(_SettingsKeys.enableNvidiaGpu),
backendUrl: getValue<String>(_SettingsKeys.backendUrl),
);
}
_initialized = true;
@@ -388,6 +394,7 @@ class IptvSettingsNotifier extends StateNotifier<IptvSettings> {
_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<IptvSettings> {
_saveToApi();
}
void setBackendUrl(String value) {
state = state.copyWith(backendUrl: value);
_saveToApi();
}
// Legacy methods
void setCategoryFilter(String filter) => setLiveTvFilter(filter);
void clearCategoryFilter() => clearLiveTvFilter();
@@ -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<XtreamService, PlaylistConfig>((ref, playlist) {
final xtreamServiceProvider =
Provider.family<XtreamService, PlaylistConfig>((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<XtreamService, PlaylistConfig>((re
/// Singleton Xtream service provider (legacy, for activeXtreamServiceProvider)
final _singletonXtreamServiceProvider = Provider<XtreamService>((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<PlaylistConfig?>((ref) => null);
final activeXtreamServiceProvider = Provider<XtreamService>((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);
});
+21 -4
View File
@@ -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')) {
@@ -17,7 +17,8 @@ class MobileSettingsTab extends ConsumerStatefulWidget {
class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
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<MobileSettingsTab> {
_liveTvController = TextEditingController();
_moviesController = TextEditingController();
_seriesController = TextEditingController();
_backendUrlController = TextEditingController();
}
@override
@@ -33,6 +35,7 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
_liveTvController.dispose();
_moviesController.dispose();
_seriesController.dispose();
_backendUrlController.dispose();
super.dispose();
}
@@ -49,6 +52,9 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
_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<MobileSettingsTab> {
),
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(
+1
View File
@@ -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