mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-22 06:28:04 +02:00
feat: introduce admin panel with user and playlist management, login screen, and IPTV settings tab
This commit is contained in:
1 parent
e8bd03e010
commit
925e8e4748
3 files changed
+135
-105
No files matched your search
@@ -17,29 +17,9 @@ final usersProvider = FutureProvider<List<AppUser>>((ref) async {
|
|||||||
return service.getAllUsers();
|
return service.getAllUsers();
|
||||||
});
|
});
|
||||||
|
|
||||||
class AdminPanel extends ConsumerStatefulWidget {
|
class AdminPanel extends StatelessWidget {
|
||||||
const AdminPanel({super.key});
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -56,64 +36,94 @@ class _AdminPanelState extends ConsumerState<AdminPanel>
|
|||||||
stops: [0.0, 1.0],
|
stops: [0.0, 1.0],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: SafeArea(
|
child: const AdminContent(),
|
||||||
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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Content
|
class AdminContent extends ConsumerStatefulWidget {
|
||||||
Expanded(
|
const AdminContent({super.key});
|
||||||
child: TabBarView(
|
|
||||||
controller: _tabController,
|
@override
|
||||||
physics: const NeverScrollableScrollPhysics(), // Custom tabs handle ref
|
ConsumerState<AdminContent> createState() => _AdminContentState();
|
||||||
children: const [
|
}
|
||||||
_PlaylistsTab(),
|
|
||||||
_UsersTab(),
|
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),
|
||||||
),
|
),
|
||||||
),
|
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(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,12 +140,25 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
|
|||||||
const SizedBox(height: 32),
|
const SizedBox(height: 32),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _usernameController,
|
controller: _usernameController,
|
||||||
style: const TextStyle(color: AppColors.textPrimary),
|
style: const TextStyle(color: Colors.white),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Username',
|
labelText: 'Username',
|
||||||
prefixIcon: const Icon(Icons.person_outline),
|
labelStyle: const TextStyle(color: Colors.white70),
|
||||||
fillColor: AppColors.surface,
|
prefixIcon: const Icon(Icons.person_outline, color: Colors.white70),
|
||||||
hoverColor: AppColors.surface.withOpacity(0.8),
|
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) {
|
validator: (value) {
|
||||||
if (value == null || value.trim().isEmpty) {
|
if (value == null || value.trim().isEmpty) {
|
||||||
@@ -160,16 +173,31 @@ class _LoginScreenState extends ConsumerState<LoginScreen> {
|
|||||||
TextFormField(
|
TextFormField(
|
||||||
controller: _passwordController,
|
controller: _passwordController,
|
||||||
obscureText: _obscurePassword,
|
obscureText: _obscurePassword,
|
||||||
style: const TextStyle(color: AppColors.textPrimary),
|
style: const TextStyle(color: Colors.white),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'Password',
|
labelText: 'Password',
|
||||||
prefixIcon: const Icon(Icons.lock_outline),
|
labelStyle: const TextStyle(color: Colors.white70),
|
||||||
fillColor: AppColors.surface,
|
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(
|
suffixIcon: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
_obscurePassword
|
_obscurePassword
|
||||||
? Icons.visibility_outlined
|
? Icons.visibility_outlined
|
||||||
: Icons.visibility_off_outlined,
|
: Icons.visibility_off_outlined,
|
||||||
|
color: Colors.white70,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import '../providers/settings_provider.dart';
|
|||||||
import 'streaming_settings_tab.dart';
|
import 'streaming_settings_tab.dart';
|
||||||
import '../../../core/providers/theme_provider.dart';
|
import '../../../core/providers/theme_provider.dart';
|
||||||
import '../../../core/theme/app_colors.dart';
|
import '../../../core/theme/app_colors.dart';
|
||||||
|
import '../../admin/screens/admin_panel.dart';
|
||||||
|
|
||||||
/// Main Settings tab with sub-tabs for Filters, Streaming, and Appearance
|
/// Main Settings tab with sub-tabs for Filters, Streaming, and Appearance
|
||||||
class SettingsTab extends ConsumerStatefulWidget {
|
class SettingsTab extends ConsumerStatefulWidget {
|
||||||
@@ -26,7 +27,8 @@ class _SettingsTabState extends ConsumerState<SettingsTab> with SingleTickerProv
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.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();
|
_liveTvController = TextEditingController();
|
||||||
_moviesController = TextEditingController();
|
_moviesController = TextEditingController();
|
||||||
_seriesController = TextEditingController();
|
_seriesController = TextEditingController();
|
||||||
@@ -83,10 +85,12 @@ class _SettingsTabState extends ConsumerState<SettingsTab> with SingleTickerProv
|
|||||||
unselectedLabelColor: Colors.white54,
|
unselectedLabelColor: Colors.white54,
|
||||||
dividerColor: Colors.transparent,
|
dividerColor: Colors.transparent,
|
||||||
overlayColor: MaterialStateProperty.all(Colors.transparent),
|
overlayColor: MaterialStateProperty.all(Colors.transparent),
|
||||||
tabs: const [
|
tabs: [
|
||||||
Tab(icon: Icon(Icons.filter_list), text: 'Filtres'),
|
const Tab(icon: Icon(Icons.filter_list), text: 'Filtres'),
|
||||||
Tab(icon: Icon(Icons.stream), text: 'Streaming'),
|
const Tab(icon: Icon(Icons.stream), text: 'Streaming'),
|
||||||
Tab(icon: Icon(Icons.palette), text: 'Apparence'),
|
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
|
// Appearance Tab
|
||||||
_buildAppearanceTab(context),
|
_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),
|
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
|
// Change playlist
|
||||||
Container(
|
Container(
|
||||||
|
|||||||
Reference in new issue
Block a user