245 lines
9.0 KiB
Dart
245 lines
9.0 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 '../../../../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)),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|