From 63846c090a1f72cad45cd42e882c280925deedec Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Mon, 8 Dec 2025 21:40:46 +0100 Subject: [PATCH] feat: implement IPTV settings management with streaming and display preferences --- bin/api/settings_handler.dart | 62 ++++++++++ bin/database/database.dart | 37 ++++++ bin/server.dart | 12 +- lib/core/services/settings_api_service.dart | 51 ++++++++ .../iptv/providers/settings_provider.dart | 113 ++++++++++++++++-- verify_settings_migration.txt | Bin 0 -> 20982 bytes verify_settings_migration_2.txt | Bin 0 -> 16172 bytes verify_settings_migration_3.txt | Bin 0 -> 84240 bytes 8 files changed, 265 insertions(+), 10 deletions(-) create mode 100644 bin/api/settings_handler.dart create mode 100644 lib/core/services/settings_api_service.dart create mode 100644 verify_settings_migration.txt create mode 100644 verify_settings_migration_2.txt create mode 100644 verify_settings_migration_3.txt diff --git a/bin/api/settings_handler.dart b/bin/api/settings_handler.dart new file mode 100644 index 0000000..febeed0 --- /dev/null +++ b/bin/api/settings_handler.dart @@ -0,0 +1,62 @@ +import 'dart:convert'; +import 'package:shelf/shelf.dart'; +import 'package:shelf_router/shelf_router.dart'; +import '../database/database.dart'; +import '../models/user.dart'; + +class SettingsHandler { + final AppDatabase db; + + SettingsHandler(this.db); + + Router get router { + final router = Router(); + + // GET /api/settings + router.get('/', (Request req) { + final user = req.context['user'] as User?; + if (user == null) { + return Response.forbidden(jsonEncode({'error': 'Authentication required'})); + } + + final settingsJson = db.getUserSettings(user.id); + + // If no settings exist, return empty object or default + return Response.ok( + settingsJson ?? '{}', + headers: {'content-type': 'application/json'}, + ); + }); + + // POST /api/settings + router.post('/', (Request req) async { + final user = req.context['user'] as User?; + if (user == null) { + return Response.forbidden(jsonEncode({'error': 'Authentication required'})); + } + + try { + final body = await req.readAsString(); + // Validate JSON + try { + jsonDecode(body); + } catch (e) { + return Response.badRequest(body: jsonEncode({'error': 'Invalid JSON'})); + } + + db.updateUserSettings(user.id, body); + + return Response.ok( + body, + headers: {'content-type': 'application/json'}, + ); + } catch (e) { + return Response.internalServerError( + body: jsonEncode({'error': 'Failed to save settings: $e'}), + ); + } + }); + + return router; + } +} diff --git a/bin/database/database.dart b/bin/database/database.dart index e4254b0..2a980f5 100644 --- a/bin/database/database.dart +++ b/bin/database/database.dart @@ -68,6 +68,16 @@ class AppDatabase { ) '''); + // User Settings table + _db.execute(''' + CREATE TABLE IF NOT EXISTS user_settings ( + user_id TEXT PRIMARY KEY, + settings_json TEXT NOT NULL, + updated_at TEXT DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE + ) + '''); + // Indexes _db.execute('CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token)'); _db.execute('CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at)'); @@ -312,6 +322,33 @@ class AppDatabase { _db.execute('DELETE FROM playlists WHERE id = ?', [playlistId]); } + // ==================== User Settings ==================== + + /// Get user settings as JSON string + String? getUserSettings(String userId) { + final result = _db.select( + 'SELECT settings_json FROM user_settings WHERE user_id = ?', + [userId], + ); + + if (result.isEmpty) return null; + return result.first['settings_json'] as String; + } + + /// Update user settings + void updateUserSettings(String userId, String settingsJson) { + final now = DateTime.now().toIso8601String(); + + // UPSERT (Insert or Replace) + _db.execute(''' + INSERT INTO user_settings (user_id, settings_json, updated_at) + VALUES (?, ?, ?) + ON CONFLICT(user_id) DO UPDATE SET + settings_json = excluded.settings_json, + updated_at = excluded.updated_at + ''', [userId, settingsJson, now]); + } + /// Close database connection void close() { _db.dispose(); diff --git a/bin/server.dart b/bin/server.dart index dccaf00..8dffec7 100644 --- a/bin/server.dart +++ b/bin/server.dart @@ -11,7 +11,10 @@ import 'database/database.dart'; import 'models/user.dart'; import 'api/auth_handler.dart'; import 'api/users_handler.dart'; +import 'api/auth_handler.dart'; +import 'api/users_handler.dart'; import 'api/playlists_handler.dart'; +import 'api/settings_handler.dart'; import 'middleware/auth_middleware.dart'; import 'middleware/security_middleware.dart'; import 'services/cleanup_service.dart'; @@ -35,6 +38,7 @@ void main(List args) async { final authHandler = AuthHandler(db); final playlistsHandler = PlaylistsHandler(db); final usersHandler = UsersHandler(db); + final settingsHandler = SettingsHandler(db); // Setup router final apiRouter = Router() @@ -43,11 +47,15 @@ void main(List args) async { // Playlists endpoints (with auth middleware) ..mount('/api/playlists', Pipeline() .addMiddleware(authMiddleware(db)) - .addHandler(playlistsHandler.router.call),) + .addHandler(playlistsHandler.router.call)) // Users endpoints (with auth middleware) ..mount('/api/users', Pipeline() .addMiddleware(authMiddleware(db)) - .addHandler(usersHandler.router.call),); + .addHandler(usersHandler.router.call)) + // Settings endpoints (with auth middleware) + ..mount('/api/settings', Pipeline() + .addMiddleware(authMiddleware(db)) + .addHandler(settingsHandler.router.call)); // Initialize Cleanup Service final cleanupService = CleanupService(); diff --git a/lib/core/services/settings_api_service.dart b/lib/core/services/settings_api_service.dart new file mode 100644 index 0000000..0ff1045 --- /dev/null +++ b/lib/core/services/settings_api_service.dart @@ -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?> 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; + } + return null; + } catch (e) { + return null; + } + } + + Future saveSettings(Map 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; + } + } +} diff --git a/lib/features/iptv/providers/settings_provider.dart b/lib/features/iptv/providers/settings_provider.dart index 82f4b1e..92dc4a7 100644 --- a/lib/features/iptv/providers/settings_provider.dart +++ b/lib/features/iptv/providers/settings_provider.dart @@ -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 { 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 _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 { ]; 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 { ); _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 _fetchFromApi() async { + try { + final remoteSettings = await _apiService.getSettings(); + if (remoteSettings != null && remoteSettings.isNotEmpty) { + // Update state with remote settings if they exist + T? getValue(String key) => remoteSettings[key] as T?; + + state = state.copyWith( + liveTvCategoryFilter: getValue(_SettingsKeys.liveTvFilter), + moviesCategoryFilter: getValue(_SettingsKeys.moviesFilter), + seriesCategoryFilter: getValue(_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(_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(_SettingsKeys.preferDirectPlay), + showClock: getValue(_SettingsKeys.showClock), + preferredAspectRatio: getValue(_SettingsKeys.preferredAspectRatio), + ); + + // Save to local prefs to keep in sync + _saveToPrefs(); + } + } catch (e) { + print('Error syncing settings from API: $e'); + } + } + + Future _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 _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 { 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 { try { _prefs ??= await SharedPreferences.getInstance(); await _prefs?.setBool(key, value); + await _saveToApi(); } catch (e) { print('Error saving setting $key: $e'); } } + Future _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 { /// Provider for IPTV settings final iptvSettingsProvider = StateNotifierProvider((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; }); diff --git a/verify_settings_migration.txt b/verify_settings_migration.txt new file mode 100644 index 0000000000000000000000000000000000000000..c5cfe4194ed189b44d7b7f6a0d2de6785e1ab9d7 GIT binary patch literal 20982 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 JrxxPb{{g5DOxged literal 0 HcmV?d00001 diff --git a/verify_settings_migration_2.txt b/verify_settings_migration_2.txt new file mode 100644 index 0000000000000000000000000000000000000000..fbfab3d6615f6c3b59cf1126753d75cd3bf47b95 GIT binary patch literal 16172 zcmds;>26$S8OIM_k+_FxD-jp)Hn!tMB5IngQ6UX(A*v#9yvK^S+T*rHh>PGpxC<_Z z3*a;G|Goct{GN5}v7Hd2(Rj{z&pFS&zsLXi_b0_{(JNjSd&OZfD7K3e{jL}L#UHi0 zUmWRpYk0Jo)_cWcJL$?%xsgqvAkE zr@G(ky<(x5E9Q&OwK`Kw>3ftXR#@vL}Mx(htU)z|7}9BdS4qGLaC(?)lh zx`(22Gx2zm_}jmr1WW^Qv7R{H(fWx#!QWQPb1QnHFTVQ4T|tCP?#jFYaTITJx_%jN z2clz;B)y(~;pIql^(6Jz;^aj5(ayAv_Y%ctlJdTu*wF8^?%tPl2KvCyO41J3GT%@! zipu#LQCXH-P`WQlH$=_4zUQKGD;*75sRMhJx_R-C?PK8z)J-M5gx2>Yxx*yGut8vS zYko876#4zAyR7pCc~(>3z^T%T#b;XYCR*{2bZf#scTpg-woY$S5NH>VPI8TS_9^CvV>(l4HRJabp_j8*STXnQm9+UhsSHT zmB9ZfD_PR@%ko6Fp`LU@7K(^v83SXS7`LbhFeR-m3^Q9-{swN9_G-*qPPEr!-rPmZ zTP=RC+~1Qm9E-lm6c-0NKhrsR0WNyQ&-H2ELvA+YeMOa?o<7&#)8adwQMGK#+NgBM zHr3Th!QNIJBAf20YB|;!RgCeRq&j9&Ho2p_eWN{#wQ$Z06CPf=G3=);o>1o$q2RaUlHx=^_q;=}K#q{ZZ z#`a)px+rzsSf_O1g3<@dQ_qWUg>6H09%{WOYni?{3mm}d!a;S^lt%vo#|zouw7Zz>A!mcJFG4$kDlP3iF&Fk>Mi7sji3W!-9WOXR{8ai!{84* zUHrLj%6=KK9S$S5U&bRay5PI=X~M_10)FthD^B3bk%77%Ylp{OS@n^)qA&0YbpUpB zsx_)$s;9afn%aPxE=pbZG%3ZNg3@eJ*2Jkag5;+p^>x`c)L~&Ed0=+o>&mKlTHp)G zRO{n532n>1qhg*ceh^g~7gT_$iwZ~WCf8WuWmHtHF?a@I7dFS)Cbq>Awk+eRB+Y=! zv(*@0ty22K$kl|tRD#|m(KcQTk(@&1i4^^LAzNHmEc;03-|F~GzxH~nP~st;NYo)K zkk8I#o9;k;RMf|>nU2&qaNwGsi6kD=>R_QgC~H$pK^<%0evog*~R; z@j6jgrvUn>etWVZyz#Zp=*+-Fwj(~!yE#rA>ugV%hrX8FMD0A9>hO&wPk%4|_LF}V z|7h?`rA`!GP0(GLhcwBL&;*bCrMol@Q!R8*&$-q;5_gB9+0%u9b}a4Df4T30lQt;8 zz{dHkrP#BkCp@z_Fa9k2Ct7!ZgC>YlcV#_H7Wnpx300ePo{q|t5Nxt;v#AYlOnu!ZjscM-Rp3e71JBcJWw- z@JQW;MnZnerL%Flq8cPx;&W8NmKwUT=0iVW5#z%rP7%)zSd{ zFFrIbuBg!P`+;IG(a_pKR>Yi%B(?CH@!>N)gLb4vIW}hljopiPY_)5 z1P#t`nF3qc`1s$Z_10mxRZzhpdP9~urscEiSaLh=cHg-jUu|NfQg#l7 z_5@aY7H#%j(KGauOsdfED(Wxqa$t*C3XzNzYO&Z&1uvZybrdj&JKmYgJz^V&=Yr*2 zk7TLy9N|wTNB8u(2MkKT(S1Dks?ixofA0JfGuJKMnfDr*ExPUilXoB;Ma@eUZ=NM! zt<`xj)BpB8kL8wGLR5d$G4AVgS3}M$SM2#pOz-eirg`kN#pH{)PE`yayIMEb$bdO+ zNDmtETXuG-A^dsw(}^xR-tXx|8`XI_QNLQBPH^4T++2!3FE#&trd_bF(r(xjX*0)n zYms?S?4U7CVOM-l9yO2#%X5Z+yd>Rnd=tp_K>RjrY^(T{V0y~)Mn_aDGxF@JRl!-{ zFI$_}`zb0`>3W_#FQSAi6HmNGbKT_Kc_TcqEY<7cxLgYG)vw8*Oh;J;tP@f3xT*}E zO6PtLg?epIwRvcs-!|_B7Q2_^l`5>yFKi`XI7XA(PWAEpe^*i`W4#ufx(!jguzOAp zz8UPx<;PC?Y{QDm6qF@cUawO2AXP5R;DYOTkAxV?yA7VV6D{edm_HIXxk6Tqh}o+} zOiL%nfBY(_pzo_v@lf#WFF^(Oki$2%&dkEtt5KP3NM}kq<_!WNUh&hw;K9d~Jt8#S|F)v5hsy3M^F?lv0FSP5r zs!Ty#dy;a+)w%;_CV3w-_eRsA(h=c@RQ)gupssTda3b7Movc)JRHn*%7}S27k{$1p zk-ex}UkOrmvQ52Jtq^-j*4VgvDC)5@$z6I zuTjk2I$Z3EDo^(79PqXjorC9&U2ucGt3vY}cBWJ6U3i|0UiOi&z4{vlZ@`O z<7olj`JFt~?_O}nL+Qq`9y^PwH_r-OHDR^qy3V}%M?D2JrprtosxhNX-xtX{upREl zJ99O>lHPoUo`Wo5~KJAiVZsh5ky*O8C;Ui>MxFJ^BlcAWa@EYFH=DSzpsGY8r zR4=S3BQ2$Oc^@?@Uq=SN`%Wc%M-<)}A-QhNdg1~kYG_A>;H%7o{VsNEc<;<}jrud? znyw3LeVrcEeM;Tx^Ii{;KljwuTD2*}d>hstc)B(F zF0H+bnk9MnoP4(0eRTImmuDHO*EvO`I^Th*YrRUr14TBv*YIdZmzZU^Z*695Du_{Y z2;w(yRyR4m4Sbi&hB;P!zE!5FE*ofV&%lpU#A=G* zOe)xcVV{A_@o^J?@HsV2^BZ2pQi92OlLIyXDGvQbK0<-+zSl&ru?oYqR908wvm?98e zXa29k6~(TF^!)wQZ_@+DJ6ZqK5zqcc8G+sr&t{61{ri_X8;zHDMu}}iOeoFOp<{t7 z`8~zbbM=GJn!Ec`tsErWP(;s9y|FR(oV_Q@v`+71*@7o{ztfQ`fS}=#;JZ_MnzU+qtsk`cKv+C~WrC9LHV*#sEnDxX zN$mx0fe z@Xi6a?xhd%^?wMx>-=4n0Ch`Q0@bU$%TT_Gd>%;8OdCC2&ij+}TD;edrVpjhmnmf*Z`ERF|KC*KeP9?>&{{;yN Bcx3

!}p7C@o_QWR!l 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∈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 literal 0 HcmV?d00001