feat: implement IPTV settings management with streaming and display preferences

This commit is contained in:
Michael committed 2025-12-08 21:40:46 +01:00
1 parent 62fc19598a
commit 63846c090a
8 files changed
+265 -10

No files matched your search

@@ -0,0 +1,51 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'dart:html' as html;
import '../models/app_user.dart';
class SettingsApiService {
final String _baseUrl = html.window.location.origin;
Future<Map<String, dynamic>?> getSettings() async {
final token = html.window.localStorage['auth_token'];
if (token == null) return null;
try {
final response = await http.get(
Uri.parse('$_baseUrl/api/settings'),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
);
if (response.statusCode == 200) {
if (response.body.isEmpty) return {};
return jsonDecode(response.body) as Map<String, dynamic>;
}
return null;
} catch (e) {
return null;
}
}
Future<bool> saveSettings(Map<String, dynamic> settings) async {
final token = html.window.localStorage['auth_token'];
if (token == null) return false;
try {
final response = await http.post(
Uri.parse('$_baseUrl/api/settings'),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
body: jsonEncode(settings),
);
return response.statusCode == 200;
} catch (e) {
return false;
}
}
}
@@ -1,5 +1,7 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../core/services/settings_api_service.dart';
import '../../auth/providers/auth_provider.dart';
/// Streaming quality presets for Live TV
enum StreamQuality {
@@ -225,28 +227,29 @@ class IptvSettings {
bool matchesFilter(String categoryName) => matchesLiveTvFilter(categoryName);
}
/// IPTV Settings notifier with SharedPreferences persistence
/// IPTV Settings notifier with SharedPreferences and API persistence
class IptvSettingsNotifier extends StateNotifier<IptvSettings> {
SharedPreferences? _prefs;
final SettingsApiService _apiService = SettingsApiService();
final Ref _ref;
bool _initialized = false;
IptvSettingsNotifier() : super(const IptvSettings()) {
IptvSettingsNotifier(this._ref) : super(const IptvSettings()) {
_loadSettings();
}
/// Load settings from SharedPreferences
/// Load settings from SharedPreferences and then API
Future<void> _loadSettings() async {
if (_initialized) return;
try {
_prefs = await SharedPreferences.getInstance();
// Load filters
// Load local settings first (fastest)
final liveTv = _prefs?.getString(_SettingsKeys.liveTvFilter) ?? '';
final movies = _prefs?.getString(_SettingsKeys.moviesFilter) ?? '';
final series = _prefs?.getString(_SettingsKeys.seriesFilter) ?? '';
// Load streaming settings
final quality = StreamQuality.values[
_prefs?.getInt(_SettingsKeys.streamQuality) ?? StreamQuality.medium.index
];
@@ -265,7 +268,6 @@ class IptvSettingsNotifier extends StateNotifier<IptvSettings> {
];
final directPlay = _prefs?.getBool(_SettingsKeys.preferDirectPlay) ?? false;
// Load Player Display Settings
final showClock = _prefs?.getBool(_SettingsKeys.showClock) ?? false;
final aspectRatio = _prefs?.getString(_SettingsKeys.preferredAspectRatio) ?? 'contain';
@@ -285,18 +287,84 @@ class IptvSettingsNotifier extends StateNotifier<IptvSettings> {
);
_initialized = true;
print('Settings loaded: Clock=$showClock, Ratio=$aspectRatio');
// Sync from API if logged in
final authState = _ref.read(authProvider);
if (authState.isAuthenticated) {
await _fetchFromApi();
}
} catch (e) {
print('Error loading settings: $e');
}
}
Future<void> _fetchFromApi() async {
try {
final remoteSettings = await _apiService.getSettings();
if (remoteSettings != null && remoteSettings.isNotEmpty) {
// Update state with remote settings if they exist
T? getValue<T>(String key) => remoteSettings[key] as T?;
state = state.copyWith(
liveTvCategoryFilter: getValue<String>(_SettingsKeys.liveTvFilter),
moviesCategoryFilter: getValue<String>(_SettingsKeys.moviesFilter),
seriesCategoryFilter: getValue<String>(_SettingsKeys.seriesFilter),
streamQuality: remoteSettings[_SettingsKeys.streamQuality] != null
? StreamQuality.values[remoteSettings[_SettingsKeys.streamQuality] as int] : null,
bufferSize: remoteSettings[_SettingsKeys.bufferSize] != null
? BufferSize.values[remoteSettings[_SettingsKeys.bufferSize] as int] : null,
connectionTimeout: remoteSettings[_SettingsKeys.connectionTimeout] != null
? ConnectionTimeout.values[remoteSettings[_SettingsKeys.connectionTimeout] as int] : null,
autoReconnect: getValue<bool>(_SettingsKeys.autoReconnect),
epgCacheDuration: remoteSettings[_SettingsKeys.epgCacheDuration] != null
? EpgCacheDuration.values[remoteSettings[_SettingsKeys.epgCacheDuration] as int] : null,
transcodingMode: remoteSettings[_SettingsKeys.transcodingMode] != null
? TranscodingMode.values[remoteSettings[_SettingsKeys.transcodingMode] as int] : null,
preferDirectPlay: getValue<bool>(_SettingsKeys.preferDirectPlay),
showClock: getValue<bool>(_SettingsKeys.showClock),
preferredAspectRatio: getValue<String>(_SettingsKeys.preferredAspectRatio),
);
// Save to local prefs to keep in sync
_saveToPrefs();
}
} catch (e) {
print('Error syncing settings from API: $e');
}
}
Future<void> _saveToApi() async {
final authState = _ref.read(authProvider);
if (!authState.isAuthenticated) return;
// Create map from state
final settingsMap = {
_SettingsKeys.liveTvFilter: state.liveTvCategoryFilter,
_SettingsKeys.moviesFilter: state.moviesCategoryFilter,
_SettingsKeys.seriesFilter: state.seriesCategoryFilter,
_SettingsKeys.streamQuality: state.streamQuality.index,
_SettingsKeys.bufferSize: state.bufferSize.index,
_SettingsKeys.connectionTimeout: state.connectionTimeout.index,
_SettingsKeys.autoReconnect: state.autoReconnect,
_SettingsKeys.epgCacheDuration: state.epgCacheDuration.index,
_SettingsKeys.transcodingMode: state.transcodingMode.index,
_SettingsKeys.preferDirectPlay: state.preferDirectPlay,
_SettingsKeys.showClock: state.showClock,
_SettingsKeys.preferredAspectRatio: state.preferredAspectRatio,
};
await _apiService.saveSettings(settingsMap);
}
// ===== Save Helpers =====
Future<void> _saveString(String key, String value) async {
try {
_prefs ??= await SharedPreferences.getInstance();
await _prefs?.setString(key, value);
await _saveToApi();
} catch (e) {
print('Error saving setting $key: $e');
}
@@ -306,6 +374,7 @@ class IptvSettingsNotifier extends StateNotifier<IptvSettings> {
try {
_prefs ??= await SharedPreferences.getInstance();
await _prefs?.setInt(key, value);
await _saveToApi();
} catch (e) {
print('Error saving setting $key: $e');
}
@@ -315,11 +384,31 @@ class IptvSettingsNotifier extends StateNotifier<IptvSettings> {
try {
_prefs ??= await SharedPreferences.getInstance();
await _prefs?.setBool(key, value);
await _saveToApi();
} catch (e) {
print('Error saving setting $key: $e');
}
}
Future<void> _saveToPrefs() async {
// Helper to bulk save state to prefs (after API fetch)
_prefs ??= await SharedPreferences.getInstance();
await _prefs?.setString(_SettingsKeys.liveTvFilter, state.liveTvCategoryFilter);
await _prefs?.setString(_SettingsKeys.moviesFilter, state.moviesCategoryFilter);
await _prefs?.setString(_SettingsKeys.seriesFilter, state.seriesCategoryFilter);
await _prefs?.setInt(_SettingsKeys.streamQuality, state.streamQuality.index);
await _prefs?.setInt(_SettingsKeys.bufferSize, state.bufferSize.index);
await _prefs?.setInt(_SettingsKeys.connectionTimeout, state.connectionTimeout.index);
await _prefs?.setBool(_SettingsKeys.autoReconnect, state.autoReconnect);
await _prefs?.setInt(_SettingsKeys.epgCacheDuration, state.epgCacheDuration.index);
await _prefs?.setInt(_SettingsKeys.transcodingMode, state.transcodingMode.index);
await _prefs?.setBool(_SettingsKeys.preferDirectPlay, state.preferDirectPlay);
await _prefs?.setBool(_SettingsKeys.showClock, state.showClock);
await _prefs?.setString(_SettingsKeys.preferredAspectRatio, state.preferredAspectRatio);
}
// ===== Filter Setters =====
void setLiveTvFilter(String filter) {
@@ -404,5 +493,13 @@ class IptvSettingsNotifier extends StateNotifier<IptvSettings> {
/// Provider for IPTV settings
final iptvSettingsProvider =
StateNotifierProvider<IptvSettingsNotifier, IptvSettings>((ref) {
return IptvSettingsNotifier();
// Watch auth provider to re-fetch settings on login/logout
final authState = ref.watch(authProvider);
final notifier = IptvSettingsNotifier(ref);
// Note: Since we watch authProvider, this provider will be disposed and recreated
// whenever authState changes. This automatically handles fetching settings for
// the new user on creation.
return notifier;
});