From f60107fac2591124246a4456fda88e1456ed7c00 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Sun, 7 Dec 2025 09:44:27 +0100 Subject: [PATCH] feat: introduce admin panel with user and playlist management, login screen, and IPTV settings tab --- lib/features/admin/screens/admin_panel.dart | 162 +++++++++++--------- lib/features/auth/screens/login_screen.dart | 42 ++++- lib/features/iptv/widgets/settings_tab.dart | 36 ++--- 3 files changed, 135 insertions(+), 105 deletions(-) diff --git a/lib/features/admin/screens/admin_panel.dart b/lib/features/admin/screens/admin_panel.dart index 20e7b75..651e988 100644 --- a/lib/features/admin/screens/admin_panel.dart +++ b/lib/features/admin/screens/admin_panel.dart @@ -17,29 +17,9 @@ final usersProvider = FutureProvider>((ref) async { return service.getAllUsers(); }); -class AdminPanel extends ConsumerStatefulWidget { +class AdminPanel extends StatelessWidget { const AdminPanel({super.key}); - @override - ConsumerState createState() => _AdminPanelState(); -} - -class _AdminPanelState extends ConsumerState - with SingleTickerProviderStateMixin { - late TabController _tabController; - - @override - void initState() { - super.initState(); - _tabController = TabController(length: 2, vsync: this); - } - - @override - void dispose() { - _tabController.dispose(); - super.dispose(); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -56,64 +36,94 @@ class _AdminPanelState extends ConsumerState stops: [0.0, 1.0], ), ), - child: SafeArea( - child: Column( - children: [ - // Custom Glass Header - Container( - padding: const EdgeInsets.all(24), - child: Row( - children: [ - GlassCard( - borderRadius: 12, - padding: const EdgeInsets.all(8), - showBorder: false, - onTap: () => context.go('/playlists'), - child: const Icon(Icons.arrow_back, color: Colors.white70), - ), - const SizedBox(width: 16), - Text( - 'Administration', - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), - const Spacer(), - // Custom Tab Bar Indicator - Container( - padding: const EdgeInsets.all(4), - decoration: BoxDecoration( - color: AppColors.surface, - borderRadius: BorderRadius.circular(50), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - _buildTabButton(0, 'Playlists', Icons.playlist_play), - const SizedBox(width: 4), - _buildTabButton(1, 'Users', Icons.people), - ], - ), - ), - ], + child: const AdminContent(), + ), + ); + } +} + +class AdminContent extends ConsumerStatefulWidget { + const AdminContent({super.key}); + + @override + ConsumerState createState() => _AdminContentState(); +} + +class _AdminContentState extends ConsumerState + with SingleTickerProviderStateMixin { + late TabController _tabController; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 2, vsync: this); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Column( + children: [ + // Custom Glass Header + Container( + padding: const EdgeInsets.all(24), + child: Row( + children: [ + // Back button only if standalone (optional, keeping for now) + GlassCard( + borderRadius: 12, + padding: const EdgeInsets.all(8), + showBorder: false, + onTap: () => context.go('/playlists'), + child: const Icon(Icons.arrow_back, color: Colors.white70), ), - ), - - // Content - Expanded( - child: TabBarView( - controller: _tabController, - physics: const NeverScrollableScrollPhysics(), // Custom tabs handle ref - children: const [ - _PlaylistsTab(), - _UsersTab(), - ], + const SizedBox(width: 16), + Text( + 'Administration', + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: Colors.white, + fontWeight: FontWeight.bold, + ), ), - ), - ], + const Spacer(), + // Custom Tab Bar Indicator + Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: AppColors.surface, + borderRadius: BorderRadius.circular(50), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + _buildTabButton(0, 'Playlists', Icons.playlist_play), + const SizedBox(width: 4), + _buildTabButton(1, 'Users', Icons.people), + ], + ), + ), + ], + ), ), - ), + + // Content + Expanded( + child: TabBarView( + controller: _tabController, + physics: const NeverScrollableScrollPhysics(), // Custom tabs handle ref + children: const [ + _PlaylistsTab(), + _UsersTab(), + ], + ), + ), + ], ), ); } diff --git a/lib/features/auth/screens/login_screen.dart b/lib/features/auth/screens/login_screen.dart index 65e2037..213aeb2 100644 --- a/lib/features/auth/screens/login_screen.dart +++ b/lib/features/auth/screens/login_screen.dart @@ -140,12 +140,25 @@ class _LoginScreenState extends ConsumerState { const SizedBox(height: 32), TextFormField( controller: _usernameController, - style: const TextStyle(color: AppColors.textPrimary), + style: const TextStyle(color: Colors.white), decoration: InputDecoration( labelText: 'Username', - prefixIcon: const Icon(Icons.person_outline), - fillColor: AppColors.surface, - hoverColor: AppColors.surface.withOpacity(0.8), + labelStyle: const TextStyle(color: Colors.white70), + prefixIcon: const Icon(Icons.person_outline, color: Colors.white70), + filled: true, + fillColor: Colors.white.withOpacity(0.05), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: Colors.white.withOpacity(0.1)), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: Colors.white.withOpacity(0.1)), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: AppColors.primary), + ), ), validator: (value) { if (value == null || value.trim().isEmpty) { @@ -160,16 +173,31 @@ class _LoginScreenState extends ConsumerState { TextFormField( controller: _passwordController, obscureText: _obscurePassword, - style: const TextStyle(color: AppColors.textPrimary), + style: const TextStyle(color: Colors.white), decoration: InputDecoration( labelText: 'Password', - prefixIcon: const Icon(Icons.lock_outline), - fillColor: AppColors.surface, + labelStyle: const TextStyle(color: Colors.white70), + prefixIcon: const Icon(Icons.lock_outline, color: Colors.white70), + filled: true, + fillColor: Colors.white.withOpacity(0.05), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: Colors.white.withOpacity(0.1)), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide(color: Colors.white.withOpacity(0.1)), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide(color: AppColors.primary), + ), suffixIcon: IconButton( icon: Icon( _obscurePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined, + color: Colors.white70, ), onPressed: () { setState(() { diff --git a/lib/features/iptv/widgets/settings_tab.dart b/lib/features/iptv/widgets/settings_tab.dart index 9c0f373..c2d1241 100644 --- a/lib/features/iptv/widgets/settings_tab.dart +++ b/lib/features/iptv/widgets/settings_tab.dart @@ -7,6 +7,7 @@ import '../providers/settings_provider.dart'; import 'streaming_settings_tab.dart'; import '../../../core/providers/theme_provider.dart'; import '../../../core/theme/app_colors.dart'; +import '../../admin/screens/admin_panel.dart'; /// Main Settings tab with sub-tabs for Filters, Streaming, and Appearance class SettingsTab extends ConsumerStatefulWidget { @@ -26,7 +27,8 @@ class _SettingsTabState extends ConsumerState with SingleTickerProv @override void initState() { super.initState(); - _tabController = TabController(length: 3, vsync: this); + final isAdmin = ref.read(authProvider).currentUser?.isAdmin ?? false; + _tabController = TabController(length: isAdmin ? 4 : 3, vsync: this); _liveTvController = TextEditingController(); _moviesController = TextEditingController(); _seriesController = TextEditingController(); @@ -83,10 +85,12 @@ class _SettingsTabState extends ConsumerState with SingleTickerProv unselectedLabelColor: Colors.white54, dividerColor: Colors.transparent, overlayColor: MaterialStateProperty.all(Colors.transparent), - tabs: const [ - Tab(icon: Icon(Icons.filter_list), text: 'Filtres'), - Tab(icon: Icon(Icons.stream), text: 'Streaming'), - Tab(icon: Icon(Icons.palette), text: 'Apparence'), + tabs: [ + const Tab(icon: Icon(Icons.filter_list), text: 'Filtres'), + const Tab(icon: Icon(Icons.stream), text: 'Streaming'), + const Tab(icon: Icon(Icons.palette), text: 'Apparence'), + if (currentUser?.isAdmin ?? false) + const Tab(icon: Icon(Icons.admin_panel_settings), text: 'Administration'), ], ), ), @@ -104,6 +108,10 @@ class _SettingsTabState extends ConsumerState with SingleTickerProv // Appearance Tab _buildAppearanceTab(context), + + // Admin Tab + if (currentUser?.isAdmin ?? false) + const AdminContent(), ], ), ), @@ -389,23 +397,7 @@ class _SettingsTabState extends ConsumerState with SingleTickerProv ), const SizedBox(height: 16), - // Admin panel - if (currentUser?.isAdmin ?? false) ...[ - Container( - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.05), - borderRadius: BorderRadius.circular(16), - border: Border.all(color: Colors.white.withOpacity(0.1)), - ), - child: ListTile( - leading: const Icon(Icons.admin_panel_settings, color: Colors.white), - title: Text('Panneau Admin', style: GoogleFonts.roboto(color: Colors.white)), - trailing: const Icon(Icons.chevron_right, color: Colors.white54), - onTap: () => context.go('/admin'), - ), - ), - const SizedBox(height: 8), - ], + // Change playlist Container(