mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-22 14:38:06 +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
+88
-9
No files matched your search
@@ -65,6 +65,7 @@ class _SettingsKeys {
|
|||||||
static const String playerType = 'player_type';
|
static const String playerType = 'player_type';
|
||||||
// Server/GPU Settings
|
// Server/GPU Settings
|
||||||
static const String enableNvidiaGpu = 'enable_nvidia_gpu';
|
static const String enableNvidiaGpu = 'enable_nvidia_gpu';
|
||||||
|
static const String backendUrl = 'backend_url';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Settings state for IPTV preferences with persistence
|
/// Settings state for IPTV preferences with persistence
|
||||||
@@ -90,6 +91,7 @@ class IptvSettings {
|
|||||||
|
|
||||||
// Server/GPU Settings
|
// Server/GPU Settings
|
||||||
final bool enableNvidiaGpu;
|
final bool enableNvidiaGpu;
|
||||||
|
final String backendUrl; // Manual override
|
||||||
|
|
||||||
const IptvSettings({
|
const IptvSettings({
|
||||||
// Filters
|
// Filters
|
||||||
@@ -110,6 +112,7 @@ class IptvSettings {
|
|||||||
this.playerType = PlayerType.standard,
|
this.playerType = PlayerType.standard,
|
||||||
// Server/GPU defaults
|
// Server/GPU defaults
|
||||||
this.enableNvidiaGpu = false,
|
this.enableNvidiaGpu = false,
|
||||||
|
this.backendUrl = '',
|
||||||
});
|
});
|
||||||
|
|
||||||
IptvSettings copyWith({
|
IptvSettings copyWith({
|
||||||
@@ -127,6 +130,7 @@ class IptvSettings {
|
|||||||
String? preferredAspectRatio,
|
String? preferredAspectRatio,
|
||||||
PlayerType? playerType,
|
PlayerType? playerType,
|
||||||
bool? enableNvidiaGpu,
|
bool? enableNvidiaGpu,
|
||||||
|
String? backendUrl,
|
||||||
}) {
|
}) {
|
||||||
return IptvSettings(
|
return IptvSettings(
|
||||||
liveTvCategoryFilter: liveTvCategoryFilter ?? this.liveTvCategoryFilter,
|
liveTvCategoryFilter: liveTvCategoryFilter ?? this.liveTvCategoryFilter,
|
||||||
@@ -143,6 +147,7 @@ class IptvSettings {
|
|||||||
preferredAspectRatio: preferredAspectRatio ?? this.preferredAspectRatio,
|
preferredAspectRatio: preferredAspectRatio ?? this.preferredAspectRatio,
|
||||||
playerType: playerType ?? this.playerType,
|
playerType: playerType ?? this.playerType,
|
||||||
enableNvidiaGpu: enableNvidiaGpu ?? this.enableNvidiaGpu,
|
enableNvidiaGpu: enableNvidiaGpu ?? this.enableNvidiaGpu,
|
||||||
|
backendUrl: backendUrl ?? this.backendUrl,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,6 +349,7 @@ class IptvSettingsNotifier extends StateNotifier<IptvSettings> {
|
|||||||
.values[remoteSettings[_SettingsKeys.playerType] as int]
|
.values[remoteSettings[_SettingsKeys.playerType] as int]
|
||||||
: null,
|
: null,
|
||||||
enableNvidiaGpu: getValue<bool>(_SettingsKeys.enableNvidiaGpu),
|
enableNvidiaGpu: getValue<bool>(_SettingsKeys.enableNvidiaGpu),
|
||||||
|
backendUrl: getValue<String>(_SettingsKeys.backendUrl),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
@@ -388,6 +394,7 @@ class IptvSettingsNotifier extends StateNotifier<IptvSettings> {
|
|||||||
_SettingsKeys.preferredAspectRatio: state.preferredAspectRatio,
|
_SettingsKeys.preferredAspectRatio: state.preferredAspectRatio,
|
||||||
_SettingsKeys.playerType: state.playerType.index,
|
_SettingsKeys.playerType: state.playerType.index,
|
||||||
_SettingsKeys.enableNvidiaGpu: state.enableNvidiaGpu,
|
_SettingsKeys.enableNvidiaGpu: state.enableNvidiaGpu,
|
||||||
|
_SettingsKeys.backendUrl: state.backendUrl,
|
||||||
};
|
};
|
||||||
|
|
||||||
print('[SettingsProvider] _saveToApi: Saving settings...');
|
print('[SettingsProvider] _saveToApi: Saving settings...');
|
||||||
@@ -487,6 +494,11 @@ class IptvSettingsNotifier extends StateNotifier<IptvSettings> {
|
|||||||
_saveToApi();
|
_saveToApi();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setBackendUrl(String value) {
|
||||||
|
state = state.copyWith(backendUrl: value);
|
||||||
|
_saveToApi();
|
||||||
|
}
|
||||||
|
|
||||||
// Legacy methods
|
// Legacy methods
|
||||||
void setCategoryFilter(String filter) => setLiveTvFilter(filter);
|
void setCategoryFilter(String filter) => setLiveTvFilter(filter);
|
||||||
void clearCategoryFilter() => clearLiveTvFilter();
|
void clearCategoryFilter() => clearLiveTvFilter();
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import '../../../core/models/playlist_config.dart';
|
import '../../../core/models/playlist_config.dart';
|
||||||
import '../services/xtream_service.dart';
|
import '../services/xtream_service.dart';
|
||||||
|
import 'settings_provider.dart';
|
||||||
|
|
||||||
/// Family provider for Xtream service - one instance per playlist
|
/// 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 service = XtreamService();
|
||||||
|
final settings = ref.watch(iptvSettingsProvider);
|
||||||
|
service.setBackendUrl(settings.backendUrl);
|
||||||
service.setPlaylist(playlist);
|
service.setPlaylist(playlist);
|
||||||
ref.onDispose(() => service.dispose());
|
ref.onDispose(() => service.dispose());
|
||||||
return service;
|
return service;
|
||||||
@@ -13,6 +17,8 @@ final xtreamServiceProvider = Provider.family<XtreamService, PlaylistConfig>((re
|
|||||||
/// Singleton Xtream service provider (legacy, for activeXtreamServiceProvider)
|
/// Singleton Xtream service provider (legacy, for activeXtreamServiceProvider)
|
||||||
final _singletonXtreamServiceProvider = Provider<XtreamService>((ref) {
|
final _singletonXtreamServiceProvider = Provider<XtreamService>((ref) {
|
||||||
final service = XtreamService();
|
final service = XtreamService();
|
||||||
|
final settings = ref.watch(iptvSettingsProvider);
|
||||||
|
service.setBackendUrl(settings.backendUrl);
|
||||||
ref.onDispose(() => service.dispose());
|
ref.onDispose(() => service.dispose());
|
||||||
return service;
|
return service;
|
||||||
});
|
});
|
||||||
@@ -39,7 +45,8 @@ final liveChannelsProvider = FutureProvider.autoDispose((ref) async {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/// Provider for live TV channels by playlist (family provider)
|
/// 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));
|
final service = ref.watch(xtreamServiceProvider(playlist));
|
||||||
return await service.getLiveChannels();
|
return await service.getLiveChannels();
|
||||||
});
|
});
|
||||||
@@ -57,7 +64,8 @@ final seriesProvider = FutureProvider.autoDispose((ref) async {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/// Provider for EPG of a specific stream
|
/// 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);
|
final service = ref.watch(activeXtreamServiceProvider);
|
||||||
return await service.getShortEpg(streamId);
|
return await service.getShortEpg(streamId);
|
||||||
});
|
});
|
||||||
@@ -82,7 +90,8 @@ class EpgRequestKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Provider for EPG with playlist context
|
/// 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));
|
final service = ref.watch(xtreamServiceProvider(key.playlist));
|
||||||
return await service.getShortEpg(key.streamId);
|
return await service.getShortEpg(key.streamId);
|
||||||
});
|
});
|
||||||
@@ -34,17 +34,34 @@ class XtreamService {
|
|||||||
_dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
|
_dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? _manualBackendUrl;
|
||||||
|
|
||||||
/// Get dynamic base URL for the local backend
|
/// Get dynamic base URL for the local backend
|
||||||
/// On Web: use localhost (proxy handles it)
|
/// Priority:
|
||||||
/// On Mobile: default to host machine IP for emulator
|
/// 1. Manual override from settings
|
||||||
|
/// 2. Detected origin (Web)
|
||||||
|
/// 3. Host machine IP (Emulator)
|
||||||
String get _backendBaseUrl {
|
String get _backendBaseUrl {
|
||||||
if (kIsWeb) return 'http://localhost:8089';
|
if (_manualBackendUrl != null && _manualBackendUrl!.isNotEmpty) {
|
||||||
// On Android emulator, 10.0.2.2 points to host machine's localhost
|
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';
|
return 'http://10.0.2.2:8089';
|
||||||
}
|
}
|
||||||
|
|
||||||
String get backendBaseUrl => _backendBaseUrl;
|
String get backendBaseUrl => _backendBaseUrl;
|
||||||
|
|
||||||
|
/// Set manual backend URL override
|
||||||
|
void setBackendUrl(String? url) {
|
||||||
|
_manualBackendUrl = url;
|
||||||
|
}
|
||||||
|
|
||||||
/// Wrap URL with proxy for all external IPTV URLs
|
/// Wrap URL with proxy for all external IPTV URLs
|
||||||
String _wrapWithProxy(String url) {
|
String _wrapWithProxy(String url) {
|
||||||
if (url.startsWith('http')) {
|
if (url.startsWith('http')) {
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ class MobileSettingsTab extends ConsumerStatefulWidget {
|
|||||||
class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
|
class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
|
||||||
late TextEditingController _liveTvController;
|
late TextEditingController _liveTvController;
|
||||||
late TextEditingController _moviesController;
|
late TextEditingController _moviesController;
|
||||||
late TextEditingController _seriesController;
|
late final TextEditingController _seriesController;
|
||||||
|
late final TextEditingController _backendUrlController;
|
||||||
bool _initialized = false;
|
bool _initialized = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -26,6 +27,7 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
|
|||||||
_liveTvController = TextEditingController();
|
_liveTvController = TextEditingController();
|
||||||
_moviesController = TextEditingController();
|
_moviesController = TextEditingController();
|
||||||
_seriesController = TextEditingController();
|
_seriesController = TextEditingController();
|
||||||
|
_backendUrlController = TextEditingController();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -33,6 +35,7 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
|
|||||||
_liveTvController.dispose();
|
_liveTvController.dispose();
|
||||||
_moviesController.dispose();
|
_moviesController.dispose();
|
||||||
_seriesController.dispose();
|
_seriesController.dispose();
|
||||||
|
_backendUrlController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +52,9 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
|
|||||||
_seriesController.text != settings.seriesCategoryFilter) {
|
_seriesController.text != settings.seriesCategoryFilter) {
|
||||||
_seriesController.text = settings.seriesCategoryFilter;
|
_seriesController.text = settings.seriesCategoryFilter;
|
||||||
}
|
}
|
||||||
|
if (!_initialized || _backendUrlController.text != settings.backendUrl) {
|
||||||
|
_backendUrlController.text = settings.backendUrl;
|
||||||
|
}
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,6 +279,40 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
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
|
// Content Filters
|
||||||
const _SectionHeader(title: 'Content Filters'),
|
const _SectionHeader(title: 'Content Filters'),
|
||||||
Container(
|
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 de l'endpoint EPG Backend (Fallback)
|
||||||
- [x] Correction du lecteur Mobile (LitePlayerView)
|
- [x] Correction du lecteur Mobile (LitePlayerView)
|
||||||
- [x] Alignement UI Mobile (Tri, EPG, URLs)
|
- [x] Alignement UI Mobile (Tri, EPG, URLs)
|
||||||
|
- [x] Correction Connectivité Mobile (Auto-origin + Manuel Override)
|
||||||
|
|
||||||
## Progress Log
|
## Progress Log
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user