From d3f6d3a65e94b3752af5be3e0510f84b81569f5b Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 26 Mar 2026 20:39:36 +0100 Subject: [PATCH] feat: Implement playlist selection screen with dynamic loading, error handling, and interactive playlist cards. --- .../screens/playlist_selection_screen.dart | 188 +++++++++--------- 1 file changed, 96 insertions(+), 92 deletions(-) diff --git a/lib/features/iptv/screens/playlist_selection_screen.dart b/lib/features/iptv/screens/playlist_selection_screen.dart index e2a7b44..9ec9b7e 100644 --- a/lib/features/iptv/screens/playlist_selection_screen.dart +++ b/lib/features/iptv/screens/playlist_selection_screen.dart @@ -21,6 +21,7 @@ class PlaylistSelectionScreen extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final currentUser = ref.watch(authProvider).currentUser; final playlistsAsync = ref.watch(playlistsProvider); + final scrollController = ScrollController(); return Scaffold( extendBodyBehindAppBar: true, @@ -37,8 +38,8 @@ class PlaylistSelectionScreen extends ConsumerWidget { actions: [ if (currentUser?.isAdmin ?? false) IconButton( - icon: - const Icon(Icons.admin_panel_settings, color: AppColors.textSecondary), + icon: const Icon(Icons.admin_panel_settings, + color: AppColors.textSecondary), onPressed: () => context.go('/admin'), tooltip: 'Admin Panel', ), @@ -109,101 +110,104 @@ class PlaylistSelectionScreen extends ConsumerWidget { ), ), ), - // Content - playlistsAsync.when( - loading: () => const Center( - child: CircularProgressIndicator(color: AppColors.primary), - ), - error: (error, stack) => Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon(Icons.error_outline, - size: 64, color: AppColors.error), - const SizedBox(height: 16), - Text( - 'Error loading playlists', - style: GoogleFonts.outfit( - fontSize: 18, - color: AppColors.textSecondary, - ), - ), - const SizedBox(height: 8), - ElevatedButton( - onPressed: () => ref.refresh(playlistsProvider), - style: ElevatedButton.styleFrom( - backgroundColor: AppColors.primary, - foregroundColor: AppColors.textPrimary, - ), - child: Text('Retry', style: GoogleFonts.inter()), - ), - ], + // Content - Explicitly filled to provide bounded constraints + Positioned.fill( + child: playlistsAsync.when( + loading: () => const Center( + child: CircularProgressIndicator(color: AppColors.primary), ), - ), - data: (playlists) { - if (playlists.isEmpty) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Icons.playlist_remove, - size: 64, - color: Colors.white24, + error: (error, stack) => Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.error_outline, + size: 64, color: AppColors.error), + const SizedBox(height: 16), + Text( + 'Error loading playlists', + style: GoogleFonts.outfit( + fontSize: 18, + color: AppColors.textSecondary, ), - const SizedBox(height: 16), - Text( - 'No playlists available', - style: GoogleFonts.outfit( - fontSize: 18, - color: Colors.white60, - ), + ), + const SizedBox(height: 8), + ElevatedButton( + onPressed: () => ref.refresh(playlistsProvider), + style: ElevatedButton.styleFrom( + backgroundColor: AppColors.primary, + foregroundColor: AppColors.textPrimary, ), - const SizedBox(height: 8), - Text( - currentUser?.isAdmin ?? false - ? 'Add playlists in Admin Panel' - : 'Contact administrator', - style: GoogleFonts.inter( - fontSize: 14, - color: Colors.white38, - ), - ), - ], - ), - ); - } - - return GridView.builder( - padding: const EdgeInsets.fromLTRB( - 48, - 100, - 48, - 48, - ), // More padding for cinematic feel - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: - MediaQuery.of(context).size.width > 1200 ? 4 : 3, - crossAxisSpacing: 32, - mainAxisSpacing: 32, - childAspectRatio: 1.4, + child: Text('Retry', style: GoogleFonts.inter()), + ), + ], ), - itemCount: playlists.length, - itemBuilder: (context, index) { - final playlist = playlists[index]; - return _PlaylistCard( - playlist: playlist, - onTap: () { - // Update current selected playlist provider first - ref.read(selectedPlaylistProvider.notifier).state = - playlist; - // Then navigate to dashboard - context.go('/dashboard'); - }, + ), + data: (playlists) { + if (playlists.isEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.playlist_remove, + size: 64, + color: Colors.white24, + ), + const SizedBox(height: 16), + Text( + 'No playlists available', + style: GoogleFonts.outfit( + fontSize: 18, + color: Colors.white60, + ), + ), + const SizedBox(height: 8), + Text( + currentUser?.isAdmin ?? false + ? 'Add playlists in Admin Panel' + : 'Contact administrator', + style: GoogleFonts.inter( + fontSize: 14, + color: Colors.white38, + ), + ), + ], + ), ); - }, - ); - }, + } + + // GridView needs a controller in some Flutter Web environments + // to avoid NaN scroll offset issues during initial layout + return GridView.builder( + controller: scrollController, + padding: const EdgeInsets.fromLTRB( + 48, + 120, // Increased top padding for AppBar + 48, + 48, + ), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: + MediaQuery.of(context).size.width > 1200 ? 4 : 3, + crossAxisSpacing: 32, + mainAxisSpacing: 32, + childAspectRatio: 1.4, + ), + itemCount: playlists.length, + itemBuilder: (context, index) { + final playlist = playlists[index]; + return _PlaylistCard( + playlist: playlist, + onTap: () { + ref.read(selectedPlaylistProvider.notifier).state = + playlist; + context.go('/dashboard'); + }, + ); + }, + ); + }, + ), ), ], ),