Files
2026-05-20 22:08:30 +01:00

243 lines
8.9 KiB
Dart

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 '../../../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 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,
activeThumbColor: 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,
activeThumbColor: 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)),
),
],
),
);
}
}