diff --git a/lib/features/iptv/screens/dashboard_screen.dart b/lib/features/iptv/screens/dashboard_screen.dart index 49a73b8..cfd4e33 100644 --- a/lib/features/iptv/screens/dashboard_screen.dart +++ b/lib/features/iptv/screens/dashboard_screen.dart @@ -10,6 +10,7 @@ import '../widgets/movies_tab.dart'; import '../widgets/series_tab.dart'; import '../widgets/settings_tab.dart'; import '../widgets/recordings_tab.dart'; +import '../widgets/playlist_selector_button.dart'; class DashboardScreen extends ConsumerStatefulWidget { final PlaylistConfig playlist; @@ -201,6 +202,12 @@ class _DashboardScreenState extends ConsumerState { const Spacer(), + // IPTV active, nommée et changeable sur place. + Padding( + padding: const EdgeInsets.only(bottom: 12), + child: PlaylistSelectorButton(current: widget.playlist), + ), + // Profil : nom d'utilisateur + déconnexion. L'avatar était // un bouton mort (onTap vide) — aucune déconnexion possible // depuis le dashboard desktop. diff --git a/lib/features/iptv/widgets/playlist_selector_button.dart b/lib/features/iptv/widgets/playlist_selector_button.dart new file mode 100644 index 0000000..a98f0a7 --- /dev/null +++ b/lib/features/iptv/widgets/playlist_selector_button.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:google_fonts/google_fonts.dart'; +import '../../../core/models/playlist_config.dart'; +import '../../../core/theme/app_colors.dart'; +import '../providers/xtream_provider.dart'; +import '../screens/playlist_selection_screen.dart'; + +/// Nomme l'IPTV active et permet d'en changer sans quitter l'onglet courant. +/// +/// Rien à l'écran ne disait sur quelle IPTV on travaillait : guide, +/// enregistrements et Auto NBA appartiennent pourtant à une playlist précise, +/// et deux playlists affichant le même contenu passaient pour un défaut du +/// filtrage alors que la sélection elle-même était invisible. +/// +/// Écrit comme `QualitySelectorButton`, l'autre sélecteur du projet : même +/// `PopupMenuButton` sous un `Tooltip`, même surface, même forme, mêmes +/// marques de sélection. +class PlaylistSelectorButton extends ConsumerWidget { + /// IPTV active, telle que l'écran la connaît déjà. + final PlaylistConfig current; + + const PlaylistSelectorButton({super.key, required this.current}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + // La liste peut n'être pas encore chargée : le menu se réduit alors à + // l'IPTV courante, qui reste nommée. + final playlists = + ref.watch(playlistsProvider).valueOrNull ?? [current]; + + return Tooltip( + message: 'IPTV : ${current.name}', + child: PopupMenuButton( + initialValue: current, + color: AppColors.surfaceContainerHigh, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + // Le menu s'ouvre à la même abscisse que celui du profil, juste en + // dessous. Son `Offset(56, -12)` est mesuré depuis un avatar de 40 + // centré dans le rail de 80, soit 20 + 56. Ce déclencheur-ci est large + // de 64, donc centré à 8 : il faut 68 pour retomber au même endroit. + offset: const Offset(68, -12), + onSelected: (playlist) { + if (playlist.id == current.id) return; + selectPlaylist(ref, playlist); + }, + itemBuilder: (context) => playlists + .map( + (playlist) => PopupMenuItem( + value: playlist, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + playlist.id == current.id + ? Icons.radio_button_checked_rounded + : Icons.radio_button_off_rounded, + size: 18, + color: playlist.id == current.id + ? AppColors.primary + : AppColors.textTertiary, + ), + const SizedBox(width: 12), + Flexible( + child: Text( + playlist.name, + overflow: TextOverflow.ellipsis, + style: GoogleFonts.karla( + color: AppColors.textPrimary, + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), + ), + ], + ), + ), + ) + .toList(), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: AppColors.surfaceContainerHigh, + borderRadius: BorderRadius.circular(16), + ), + child: const Icon( + Icons.playlist_play, + color: AppColors.primary, + size: 24, + ), + ), + const SizedBox(height: 4), + // Le nom tient sous l'icône : c'est lui l'information, le reste + // n'est que la poignée pour en changer. + SizedBox( + width: 64, + child: Text( + current.name, + textAlign: TextAlign.center, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: GoogleFonts.karla( + color: AppColors.textTertiary, + fontSize: 11, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/features/iptv/widgets/settings_tab.dart b/lib/features/iptv/widgets/settings_tab.dart index 3ae46d7..3f28d76 100644 --- a/lib/features/iptv/widgets/settings_tab.dart +++ b/lib/features/iptv/widgets/settings_tab.dart @@ -5,6 +5,7 @@ import 'package:google_fonts/google_fonts.dart'; import 'dart:html' as html; // For reloading import '../../auth/providers/auth_provider.dart'; import '../providers/settings_provider.dart'; +import '../providers/xtream_provider.dart'; import 'streaming_settings_tab.dart'; import '../../../core/providers/theme_provider.dart'; import '../../../core/theme/app_colors.dart'; @@ -432,6 +433,16 @@ class _SettingsTabState extends ConsumerState 'Changer de Playlist', style: GoogleFonts.karla(color: AppColors.onSurface), ), + // L'IPTV active, pour savoir d'où viennent guide, enregistrements + // et Auto NBA avant d'en changer. + subtitle: Text( + ref.watch(selectedPlaylistProvider)?.name ?? 'Aucune', + overflow: TextOverflow.ellipsis, + style: GoogleFonts.karla( + color: AppColors.textTertiary, + fontSize: 11, + ), + ), trailing: const Icon(Icons.chevron_right, color: AppColors.onSurfaceVariant), onTap: () => context.go('/playlists'), ), diff --git a/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart b/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart index 1d368f6..c6d228a 100644 --- a/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart +++ b/lib/mobile/features/iptv/widgets/mobile_settings_tab.dart @@ -4,6 +4,7 @@ import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import '../../../../features/auth/providers/auth_provider.dart'; import '../../../../features/iptv/providers/settings_provider.dart'; +import '../../../../features/iptv/providers/xtream_provider.dart'; import '../../../../core/providers/theme_provider.dart'; import '../../../../core/theme/app_colors.dart'; @@ -364,6 +365,9 @@ class _MobileSettingsTabState extends ConsumerState { _SettingsButton( icon: Icons.playlist_play, label: 'Change Playlist', + // L'IPTV active, pour savoir d'où viennent guide, enregistrements + // et Auto NBA avant d'en changer. + value: ref.watch(selectedPlaylistProvider)?.name, onTap: () => context.go('/playlists'), ), const SizedBox(height: 32), @@ -461,12 +465,16 @@ class _FilterInput extends StatelessWidget { class _SettingsButton extends StatelessWidget { final IconData icon; final String label; + + /// Valeur courante de ce réglage, affichée sous le libellé. + final String? value; final VoidCallback onTap; const _SettingsButton({ required this.icon, required this.label, required this.onTap, + this.value, }); @override @@ -484,12 +492,27 @@ class _SettingsButton extends StatelessWidget { children: [ Icon(icon, color: AppColors.onSurface), const SizedBox(width: 16), - Text( - label, - style: const TextStyle( - fontWeight: FontWeight.w600, - color: AppColors.onSurface, - ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + label, + style: const TextStyle( + fontWeight: FontWeight.w600, + color: AppColors.onSurface, + ), + ), + if (value != null) + Text( + value!, + overflow: TextOverflow.ellipsis, + style: GoogleFonts.karla( + color: AppColors.textTertiary, + fontSize: 11, + ), + ), + ], ), const Spacer(), const Icon(Icons.chevron_right, color: AppColors.onSurfaceVariant),