first commit
This commit is contained in:
244
lib/features/admin/presentation/screens/admin_screen.dart
Normal file
244
lib/features/admin/presentation/screens/admin_screen.dart
Normal file
@@ -0,0 +1,244 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
import '../../../../core/widgets/riotz_scaffold.dart';
|
||||
import '../../../../core/widgets/riotz_button.dart';
|
||||
import '../../../music/domain/models/track_model.dart';
|
||||
import '../providers/admin_providers.dart';
|
||||
import '../../domain/models/admin_user_model.dart';
|
||||
import '../../domain/models/admin_post_model.dart';
|
||||
|
||||
class AdminScreen extends ConsumerWidget {
|
||||
const AdminScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
final dataAsync = ref.watch(adminPanelDataProvider);
|
||||
|
||||
ref.listen(adminControllerProvider, (_, next) {
|
||||
next.whenOrNull(
|
||||
data: (_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
backgroundColor: AppColors.success,
|
||||
content: Text('SYSTEM OVERRIDE SUCCESSFUL.', style: TextStyle(color: AppColors.black, fontWeight: FontWeight.bold)),
|
||||
),
|
||||
);
|
||||
},
|
||||
error: (error, _) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: AppColors.bloodRed,
|
||||
content: Text('ERROR: ${error.toString().toUpperCase()}'),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
return RiotzScaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('RIOTZ // TERMINAL'),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new, size: 20),
|
||||
onPressed: () => context.pop(),
|
||||
),
|
||||
),
|
||||
body: dataAsync.when(
|
||||
loading: () => const Center(child: CircularProgressIndicator(color: AppColors.neonRed)),
|
||||
error: (error, _) => Center(child: Text('TERMINAL OFFLINE: $error')),
|
||||
data: (data) => RefreshIndicator(
|
||||
onRefresh: () async => ref.invalidate(adminPanelDataProvider),
|
||||
color: AppColors.neonRed,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24),
|
||||
children: [
|
||||
_SectionHeader(title: 'USER MODERATION', count: data.users.length),
|
||||
const SizedBox(height: 16),
|
||||
...data.users.map((user) => _AdminUserCard(user: user)),
|
||||
|
||||
const SizedBox(height: 48),
|
||||
_SectionHeader(title: 'TRACK MODERATION', count: data.tracks.length),
|
||||
const SizedBox(height: 16),
|
||||
...data.tracks.map((track) => _AdminTrackCard(track: track)),
|
||||
|
||||
const SizedBox(height: 48),
|
||||
_SectionHeader(title: 'POST MODERATION', count: data.posts.length),
|
||||
const SizedBox(height: 16),
|
||||
...data.posts.map((post) => _AdminPostCard(post: post)),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SectionHeader extends StatelessWidget {
|
||||
const _SectionHeader({required this.title, required this.count});
|
||||
final String title;
|
||||
final int count;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: theme.textTheme.labelLarge?.copyWith(letterSpacing: 2, color: AppColors.neonRed)),
|
||||
const Divider(color: AppColors.neonRed, thickness: 2),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AdminUserCard extends ConsumerWidget {
|
||||
const _AdminUserCard({required this.user});
|
||||
final AdminUserModel user;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
border: Border.all(color: user.banned ? AppColors.bloodRed : AppColors.border),
|
||||
),
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: AppColors.surfaceLight,
|
||||
backgroundImage: user.avatarUrl.isNotEmpty ? NetworkImage(user.avatarUrl) : null,
|
||||
child: user.avatarUrl.isEmpty ? const Icon(Icons.person, color: AppColors.grey) : null,
|
||||
),
|
||||
title: Text(user.username.toUpperCase(), style: theme.textTheme.labelLarge),
|
||||
subtitle: Text(user.banned ? 'STATUS: BANNED' : (user.featured ? 'STATUS: FEATURED ARTIST' : 'STATUS: ACTIVE'),
|
||||
style: TextStyle(color: user.banned ? AppColors.neonRed : (user.featured ? AppColors.success : AppColors.grey), fontSize: 10, fontWeight: FontWeight.bold)),
|
||||
trailing: PopupMenuButton<String>(
|
||||
icon: const Icon(Icons.more_vert, color: AppColors.white),
|
||||
color: AppColors.surfaceLight,
|
||||
onSelected: (value) {
|
||||
if (value == 'ban') {
|
||||
ref.read(adminControllerProvider.notifier).setUserBanned(userId: user.userId, banned: !user.banned);
|
||||
} else if (value == 'feature') {
|
||||
ref.read(adminControllerProvider.notifier).setUserFeatured(userId: user.userId, featured: !user.featured);
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 'ban',
|
||||
child: Text(user.banned ? 'UNBAN USER' : 'BAN USER', style: const TextStyle(color: AppColors.neonRed)),
|
||||
),
|
||||
PopupMenuItem(
|
||||
value: 'feature',
|
||||
child: Text(user.featured ? 'UNFEATURE ARTIST' : 'FEATURE ARTIST'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AdminTrackCard extends ConsumerWidget {
|
||||
const _AdminTrackCard({required this.track});
|
||||
final TrackModel track;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
border: Border.all(color: track.featured ? AppColors.success : AppColors.border),
|
||||
),
|
||||
child: ListTile(
|
||||
leading: Icon(Icons.music_note, color: track.featured ? AppColors.success : AppColors.white),
|
||||
title: Text(track.title.toUpperCase(), style: theme.textTheme.labelLarge),
|
||||
subtitle: Text('BY: ${track.username.toUpperCase()}', style: const TextStyle(fontSize: 10, color: AppColors.grey)),
|
||||
trailing: Switch(
|
||||
value: track.featured,
|
||||
activeColor: AppColors.success,
|
||||
onChanged: (val) {
|
||||
ref.read(adminControllerProvider.notifier).setTrackFeatured(trackId: track.id, featured: val);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AdminPostCard extends ConsumerWidget {
|
||||
const _AdminPostCard({required this.post});
|
||||
final AdminPostModel post;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
border: Border.all(color: post.featured ? AppColors.neonRed : AppColors.border),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text(post.username.toUpperCase(), style: theme.textTheme.labelLarge),
|
||||
subtitle: Text('ID: ${post.id.substring(0, 8)}...', style: const TextStyle(fontSize: 10)),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.delete_forever, color: AppColors.neonRed),
|
||||
onPressed: () => _confirmDelete(context, ref),
|
||||
),
|
||||
),
|
||||
if (post.imageUrl.isNotEmpty)
|
||||
Image.network(post.imageUrl, height: 120, width: double.infinity, fit: BoxFit.cover),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
children: [
|
||||
const Text('FEATURE CONTENT', style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold)),
|
||||
const Spacer(),
|
||||
Switch(
|
||||
value: post.featured,
|
||||
activeColor: AppColors.neonRed,
|
||||
onChanged: (val) {
|
||||
ref.read(adminControllerProvider.notifier).setPostFeatured(postId: post.id, featured: val);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _confirmDelete(BuildContext context, WidgetRef ref) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: AppColors.surface,
|
||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.zero),
|
||||
title: const Text('ERASE DATA?'),
|
||||
content: const Text('THIS WILL REMOVE THE CONTENT FROM THE NETWORK PERMANENTLY.'),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(context), child: const Text('CANCEL')),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
ref.read(adminControllerProvider.notifier).deletePost(post.id);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text('DELETE', style: TextStyle(color: AppColors.neonRed)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user