mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-11 17:30:00 +02:00
feat: introduce IPTV settings management with a dedicated mobile tab, providers, and service.
This commit is contained in:
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);
|
||||
});
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user