feat: introduce admin panel with user and playlist management, login screen, and IPTV settings tab

This commit is contained in:
Michael committed 2025-12-07 09:44:27 +01:00
1 parent 6c6123e405
commit f60107fac2
3 files changed
+135 -105

No files matched your search

+86 -76
View File
@@ -17,29 +17,9 @@ final usersProvider = FutureProvider<List<AppUser>>((ref) async {
return service.getAllUsers();
});
class AdminPanel extends ConsumerStatefulWidget {
class AdminPanel extends StatelessWidget {
const AdminPanel({super.key});
@override
ConsumerState<AdminPanel> createState() => _AdminPanelState();
}
class _AdminPanelState extends ConsumerState<AdminPanel>
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<AdminPanel>
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<AdminContent> createState() => _AdminContentState();
}
class _AdminContentState extends ConsumerState<AdminContent>
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(),
],
),
),
],
),
);
}
+35 -7
View File
@@ -140,12 +140,25 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
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<LoginScreen> {
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(() {
+14 -22
View File
@@ -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<SettingsTab> 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<SettingsTab> 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<SettingsTab> with SingleTickerProv
// Appearance Tab
_buildAppearanceTab(context),
// Admin Tab
if (currentUser?.isAdmin ?? false)
const AdminContent(),
],
),
),
@@ -389,23 +397,7 @@ class _SettingsTabState extends ConsumerState<SettingsTab> 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(