From 6368fd6ee2e0d6c62bd053fdba2f33161c5db083 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 19 Oct 2026 18:16:35 +0200 Subject: [PATCH] =?UTF-8?q?Multi-IPTV=20:=20d=C3=A9clarer=20l'IPTV=20activ?= =?UTF-8?q?e=20de=20fa=C3=A7on=20synchrone=20dans=20AuthedHttp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AuthedHttp lisait l'IPTV dans localStorage, écrit par un écouteur Riverpod enregistré tardivement : après un changement d'IPTV l'en-tête X-Playlist-Id pouvait rester périmé. AuthedHttp.usePlaylist pose l'IPTV en mémoire dès la sélection et à chaque construction du tableau de bord. Auto NBA reçoit la playlist, recharge sa règle quand elle change et nomme l'IPTV sur laquelle les matchs sont enregistrés. Co-Authored-By: Claude Opus 5.5 --- lib/core/api/authed_http.dart | 26 ++++++++++++++++--- .../iptv/screens/dashboard_screen.dart | 3 +++ .../screens/playlist_selection_screen.dart | 5 ++++ lib/features/iptv/widgets/recordings_tab.dart | 26 ++++++++++++++++--- .../iptv/screens/mobile_dashboard_screen.dart | 3 +++ .../mobile_playlist_selection_screen.dart | 2 ++ 6 files changed, 58 insertions(+), 7 deletions(-) diff --git a/lib/core/api/authed_http.dart b/lib/core/api/authed_http.dart index 3e14b95..127b9c3 100644 --- a/lib/core/api/authed_http.dart +++ b/lib/core/api/authed_http.dart @@ -24,12 +24,30 @@ class AuthedHttp { return (t != null && t.isNotEmpty) ? t : null; } - /// IPTV sélectionnée (persistée par `playlistPersistenceProvider`). + /// IPTV active, posée de façon synchrone par l'écran de sélection et le + /// tableau de bord (voir [usePlaylist]). /// - /// Envoyée à chaque appel : sans elle le serveur prenait la playlist la - /// plus récente, et guide, enregistrements et Auto NBA visaient la - /// mauvaise IPTV quand plusieurs étaient configurées. + /// POURQUOI : `localStorage['selected_playlist']` n'est écrit que par un + /// écouteur Riverpod enregistré tardivement ; après un changement d'IPTV + /// il restait périmé, le serveur retombait sur la playlist par défaut et + /// les deux IPTV partageaient la même règle Auto NBA et les mêmes + /// enregistrements. + static String? _currentPlaylistId; + + /// Déclare l'IPTV active et la persiste immédiatement. + static void usePlaylist(String id, {Map? json}) { + if (id.isEmpty) return; + _currentPlaylistId = id; + if (json != null) { + try { + html.window.localStorage['selected_playlist'] = jsonEncode(json); + } catch (_) {} + } + } + + /// IPTV envoyée au serveur : celle déclarée, sinon la dernière persistée. static String? get selectedPlaylistId { + if (_currentPlaylistId != null) return _currentPlaylistId; try { final stored = html.window.localStorage['selected_playlist']; if (stored == null) return null; diff --git a/lib/features/iptv/screens/dashboard_screen.dart b/lib/features/iptv/screens/dashboard_screen.dart index cfd4e33..11b7725 100644 --- a/lib/features/iptv/screens/dashboard_screen.dart +++ b/lib/features/iptv/screens/dashboard_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../auth/providers/auth_provider.dart'; +import '../../../core/api/authed_http.dart'; import '../../../core/models/playlist_config.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/widgets/glass_container.dart'; @@ -43,6 +44,8 @@ class _DashboardScreenState extends ConsumerState { @override Widget build(BuildContext context) { + // Toutes les requêtes /api/* de ce tableau de bord visent cette IPTV. + AuthedHttp.usePlaylist(widget.playlist.id, json: widget.playlist.toJson()); return Scaffold( backgroundColor: AppColors.background, body: Stack( diff --git a/lib/features/iptv/screens/playlist_selection_screen.dart b/lib/features/iptv/screens/playlist_selection_screen.dart index 623a1d7..182b95f 100644 --- a/lib/features/iptv/screens/playlist_selection_screen.dart +++ b/lib/features/iptv/screens/playlist_selection_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import '../../../core/services/playlist_api_service.dart'; +import '../../../core/api/authed_http.dart'; import '../../../core/models/playlist_config.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_theme.dart'; @@ -163,6 +164,10 @@ class PlaylistSelectionScreen extends ConsumerWidget { child: _PlaylistCard( playlist: playlist, onTap: () { + AuthedHttp.usePlaylist( + playlist.id, + json: playlist.toJson(), + ); selectPlaylist(ref, playlist); context.go('/dashboard'); }, diff --git a/lib/features/iptv/widgets/recordings_tab.dart b/lib/features/iptv/widgets/recordings_tab.dart index 41469c6..eade6c7 100644 --- a/lib/features/iptv/widgets/recordings_tab.dart +++ b/lib/features/iptv/widgets/recordings_tab.dart @@ -52,6 +52,8 @@ class _RecordingsTabState extends State @override Widget build(BuildContext context) { + // Guide, enregistrements, season passes et Auto NBA visent cette IPTV. + AuthedHttp.usePlaylist(widget.playlist.id); return Container( color: Colors.black, child: Column( @@ -112,7 +114,7 @@ class _RecordingsTabState extends State _EpgGuideView(playlist: widget.playlist), _RecordingsListView(playlist: widget.playlist), const _SeasonPassesView(), - const _AutoNbaView(), + _AutoNbaView(playlist: widget.playlist), ], ), ), @@ -1752,7 +1754,9 @@ const _mois = [ ]; class _AutoNbaView extends StatefulWidget { - const _AutoNbaView(); + /// IPTV dont la règle est affichée et qui capture les matchs choisis. + final PlaylistConfig playlist; + const _AutoNbaView({required this.playlist}); @override State<_AutoNbaView> createState() => _AutoNbaViewState(); } @@ -1773,6 +1777,21 @@ class _AutoNbaViewState extends State<_AutoNbaView> { _load(); } + @override + void didUpdateWidget(covariant _AutoNbaView oldWidget) { + super.didUpdateWidget(oldWidget); + // Autre IPTV : autre règle, autres matchs choisis. + if (oldWidget.playlist.id != widget.playlist.id) { + AuthedHttp.usePlaylist(widget.playlist.id); + setState(() { + _rule = null; + _games = []; + _diag = null; + }); + _load(); + } + } + Future _load() async { setState(() { _isLoading = _rule == null; @@ -2427,7 +2446,8 @@ class _AutoNbaViewState extends State<_AutoNbaView> { children: [ Expanded( child: Text( - 'Matchs NBA en direct sur beIN Sports FR', + 'Matchs NBA en direct sur beIN Sports FR · ' + 'enregistrés sur « ${widget.playlist.name} »', style: GoogleFonts.fraunces( color: AppColors.onSurface54, fontSize: 13, diff --git a/lib/mobile/features/iptv/screens/mobile_dashboard_screen.dart b/lib/mobile/features/iptv/screens/mobile_dashboard_screen.dart index 9753bc9..3f998b2 100644 --- a/lib/mobile/features/iptv/screens/mobile_dashboard_screen.dart +++ b/lib/mobile/features/iptv/screens/mobile_dashboard_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../../../core/api/authed_http.dart'; import '../../../../core/models/playlist_config.dart'; import '../../../widgets/mobile_scaffold.dart'; import '../../../theme/mobile_theme.dart'; @@ -26,6 +27,8 @@ class _MobileDashboardScreenState extends ConsumerState { @override Widget build(BuildContext context) { + // Toutes les requêtes /api/* de ce tableau de bord visent cette IPTV. + AuthedHttp.usePlaylist(widget.playlist.id, json: widget.playlist.toJson()); return Theme( data: MobileTheme.darkTheme, child: MobileScaffold( diff --git a/lib/mobile/features/iptv/screens/mobile_playlist_selection_screen.dart b/lib/mobile/features/iptv/screens/mobile_playlist_selection_screen.dart index 7d8356d..4f49d9b 100644 --- a/lib/mobile/features/iptv/screens/mobile_playlist_selection_screen.dart +++ b/lib/mobile/features/iptv/screens/mobile_playlist_selection_screen.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import '../../../../features/iptv/providers/xtream_provider.dart'; import 'package:google_fonts/google_fonts.dart'; import '../../../../core/services/playlist_api_service.dart'; +import '../../../../core/api/authed_http.dart'; import '../../../../core/models/playlist_config.dart'; import '../../../../features/auth/providers/auth_provider.dart'; import '../../../../core/theme/app_colors.dart'; @@ -108,6 +109,7 @@ class MobilePlaylistSelectionScreen extends ConsumerWidget { return _MobilePlaylistCard( playlist: playlist, onTap: () { + AuthedHttp.usePlaylist(playlist.id, json: playlist.toJson()); // Choix + persistance dans le même geste : l'en-tête // X-Playlist-Id doit partir dès la première requête. selectPlaylist(ref, playlist);