Initial commit

This commit is contained in:
St. Nebula
2026-04-23 23:58:59 -05:00
commit 47b9e3c159
257 changed files with 18913 additions and 0 deletions
+259
View File
@@ -0,0 +1,259 @@
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:onsolgo/core/constants.dart';
import 'dart:math';
class AdminDashboard extends StatelessWidget {
const AdminDashboard({super.key});
// --- LOGIC: SYSTEM BROADCAST ---
void _editAnnouncement(BuildContext context) async {
final tC = TextEditingController();
final mC = TextEditingController();
bool active = true;
try {
var existing = await FirebaseFirestore.instance.collection('system').doc('announcement').get();
if (existing.exists) {
final map = existing.data();
if (map != null) {
tC.text = map['title']?.toString() ?? '';
mC.text = map['message']?.toString() ?? '';
active = map['isActive'] as bool? ?? true;
}
}
} catch (e) {
debugPrint("Load Error: $e");
}
if (!context.mounted) return;
showDialog(
context: context,
builder: (ctx) => StatefulBuilder(
builder: (ctx, setS) => AlertDialog(
backgroundColor: Colors.grey[900],
title: const Text("SYSTEM BROADCAST", style: TextStyle(color: kOnsolGold, fontSize: 14, fontWeight: FontWeight.bold)),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(controller: tC, decoration: const InputDecoration(hintText: "Headline")),
const SizedBox(height: 10),
TextField(controller: mC, maxLines: 3, decoration: const InputDecoration(hintText: "Message")),
SwitchListTile(
title: const Text("Show Popup", style: TextStyle(fontSize: 12)),
value: active,
activeThumbColor: kOnsolGold,
onChanged: (v) => setS(() => active = v)
),
],
),
actions: [
TextButton(onPressed: () => Navigator.pop(ctx), child: const Text("CANCEL", style: TextStyle(color: Colors.grey))),
ElevatedButton(
onPressed: () async {
await FirebaseFirestore.instance.collection('system').doc('announcement').set({
'title': tC.text.trim(),
'message': mC.text.trim(),
'isActive': active,
'updatedAt': FieldValue.serverTimestamp()
});
if (context.mounted) Navigator.pop(ctx);
},
child: const Text("TRANSMIT"),
)
],
),
),
);
}
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 4,
child: Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
backgroundColor: Colors.red[900],
title: const Text("COMMAND CENTER", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)),
actions: [
IconButton(icon: const Icon(Icons.podcasts), onPressed: () => _editAnnouncement(context))
],
bottom: const TabBar(
labelStyle: TextStyle(fontSize: 8, fontWeight: FontWeight.bold),
indicatorColor: Colors.white,
tabs: [
Tab(text: "SERIES", icon: Icon(Icons.library_books, size: 18)),
Tab(text: "MERCH", icon: Icon(Icons.shopping_bag, size: 18)),
Tab(text: "CITIZENS", icon: Icon(Icons.people, size: 18)),
Tab(text: "INVITES", icon: Icon(Icons.vpn_key, size: 18)),
],
),
),
body: const TabBarView(
children: [
_ManageSeriesTab(),
_ManageMerchTab(),
_ManageUsersTab(),
_ManageInvitesTab(),
],
),
),
);
}
}
// --- TAB 3: CITIZEN MODERATION (Energy & Ranks) ---
class _ManageUsersTab extends StatelessWidget {
const _ManageUsersTab();
void _showModeration(BuildContext context, DocumentSnapshot user) {
var d = user.data() as Map<String, dynamic>;
int rank = safeInt(d['rankLevel'] ?? 1);
bool ver = d['isVerified'] ?? (rank == 5);
bool ban = d['isBanned'] ?? false;
showDialog(
context: context,
builder: (ctx) => StatefulBuilder(
builder: (ctx, setS) => AlertDialog(
backgroundColor: Colors.grey[900],
title: Text("MODERATING: ${d['username'] ?? 'Citizen'}"),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
DropdownButton<int>(
value: rank,
isExpanded: true,
dropdownColor: Colors.black,
items: [1, 2, 3, 4, 5].map((v) => DropdownMenuItem(value: v, child: Text(getRankName(v)))).toList(),
onChanged: (v) => setS(() {
rank = v!;
if (rank == 5) ver = true;
}),
),
SwitchListTile(title: const Text("Verified"), value: ver, activeThumbColor: kOnsolGold, onChanged: (v) => setS(() => ver = v)),
SwitchListTile(title: const Text("Exiled"), value: ban, activeThumbColor: Colors.red, onChanged: (v) => setS(() => ban = v)),
const SizedBox(height: 10),
// --- ENERGY REFILL BUTTON (2/2) ---
ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.orangeAccent, foregroundColor: Colors.black, minimumSize: const Size(double.infinity, 40)),
onPressed: () async {
await user.reference.update({
'energy': 2,
'lastEnergyRefill': FieldValue.serverTimestamp()
});
if (ctx.mounted) Navigator.pop(ctx);
},
child: const Text("REFILL ENERGY (2/2)", style: TextStyle(fontWeight: FontWeight.bold))
),
],
),
),
actions: [
TextButton(onPressed: () => Navigator.pop(ctx), child: const Text("CANCEL")),
ElevatedButton(
onPressed: () async {
await user.reference.update({'rankLevel': rank, 'isVerified': ver, 'isBanned': ban});
if (ctx.mounted) Navigator.pop(ctx);
},
child: const Text("APPLY"),
)
],
),
),
);
}
@override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection('users').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Center(child: CircularProgressIndicator());
return ListView(
padding: const EdgeInsets.all(15),
children: snapshot.data!.docs.map((u) {
var d = u.data() as Map<String, dynamic>;
int r = safeInt(d['rankLevel'] ?? 1);
bool isBan = d['isBanned'] ?? false;
return Card(
color: Colors.grey[900],
child: ListTile(
leading: Icon(getRankIcon(r), color: isBan ? Colors.red : getRankColor(r)),
title: Row(children: [
Text(d['username'] ?? 'Anonymous', style: TextStyle(color: isBan ? Colors.red : Colors.white)),
verifiedBadge(d['isVerified'] ?? (r == 5))
]),
subtitle: Text(isBan ? "EXILED" : getRankName(r)),
trailing: IconButton(icon: const Icon(Icons.shield, color: Colors.amber), onPressed: () => _showModeration(context, u)),
),
);
}).toList(),
);
},
);
}
}
// --- OTHER TABS ---
class _ManageSeriesTab extends StatelessWidget {
const _ManageSeriesTab();
void _add(BuildContext context) {
final t = TextEditingController();
showDialog(context: context, builder: (ctx) => AlertDialog(
backgroundColor: Colors.grey[900], title: const Text("New Series"),
content: TextField(controller: t, decoration: const InputDecoration(hintText: "Series Title")),
actions: [ElevatedButton(onPressed: () { FirebaseFirestore.instance.collection('manga').add({'title': t.text, 'reads': 0, 'readingMode': 'RL', 'author': 'Artist', 'coverUrl': ''}); Navigator.pop(ctx); }, child: const Text("ADD"))],
));
}
@override
Widget build(BuildContext context) {
return Scaffold(backgroundColor: Colors.transparent, floatingActionButton: FloatingActionButton(backgroundColor: Colors.white, child: const Icon(Icons.add, color: Colors.black), onPressed: () => _add(context)), body: StreamBuilder<QuerySnapshot>(stream: FirebaseFirestore.instance.collection('manga').snapshots(), builder: (context, snapshot) { if (!snapshot.hasData) return const CircularProgressIndicator(); return ListView(padding: const EdgeInsets.all(15), children: snapshot.data!.docs.map((doc) => Card(color: Colors.grey[900], child: ListTile(title: Text(doc['title'] ?? 'Untitled'), trailing: IconButton(icon: const Icon(Icons.delete_outline, color: Colors.red), onPressed: () => doc.reference.delete())))).toList()); }));
}
}
class _ManageMerchTab extends StatelessWidget {
const _ManageMerchTab();
void _add(BuildContext context) {
final n = TextEditingController(); final p = TextEditingController(); final u = TextEditingController(); String cat = 'vault';
showDialog(context: context, builder: (ctx) => StatefulBuilder(builder: (context, setS) => AlertDialog(backgroundColor: Colors.grey[900], title: const Text("New Item"),
content: SingleChildScrollView(child: Column(mainAxisSize: MainAxisSize.min, children: [TextField(controller: n, decoration: const InputDecoration(hintText: "Name")), TextField(controller: p, decoration: const InputDecoration(hintText: "Price")), TextField(controller: u, decoration: const InputDecoration(hintText: "Buy URL")), DropdownButton<String>(value: cat, isExpanded: true, dropdownColor: Colors.black, items: ['vault', 'prints', 'merch'].map((s) => DropdownMenuItem(value: s, child: Text(s.toUpperCase()))).toList(), onChanged: (v) => setS(() => cat = v!))])),
actions: [ElevatedButton(onPressed: () { FirebaseFirestore.instance.collection('marketplace').add({'name': n.text, 'price': p.text, 'buyUrl': u.text, 'category': cat, 'imageUrl': ''}); Navigator.pop(ctx); }, child: const Text("ADD"))],
)));
}
@override
Widget build(BuildContext context) {
return Scaffold(backgroundColor: Colors.transparent, floatingActionButton: FloatingActionButton(backgroundColor: kOnsolGold, child: const Icon(Icons.add_shopping_cart, color: Colors.black), onPressed: () => _add(context)), body: StreamBuilder<QuerySnapshot>(stream: FirebaseFirestore.instance.collection('marketplace').snapshots(), builder: (context, snapshot) { if (!snapshot.hasData) return const CircularProgressIndicator(); return ListView(padding: const EdgeInsets.all(15), children: snapshot.data!.docs.map((doc) => Card(color: Colors.grey[900], child: ListTile(title: Text(doc['name'] ?? 'Item'), subtitle: Text(doc['price'] ?? ''), trailing: IconButton(icon: const Icon(Icons.delete_outline, color: Colors.red), onPressed: () => doc.reference.delete())))).toList()); }));
}
}
class _ManageInvitesTab extends StatelessWidget {
const _ManageInvitesTab();
@override
Widget build(BuildContext context) {
return Column(children: [
const SizedBox(height: 20),
ElevatedButton(style: ElevatedButton.styleFrom(backgroundColor: Colors.white, foregroundColor: Colors.black), onPressed: () {
String code = List.generate(6, (i) => "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"[Random().nextInt(30)]).join();
FirebaseFirestore.instance.collection('invite_codes').doc(code).set({'isUsed': false, 'createdAt': FieldValue.serverTimestamp()});
}, child: const Text("GENERATE INVITE")),
Expanded(child: StreamBuilder<QuerySnapshot>(stream: FirebaseFirestore.instance.collection('invite_codes').snapshots(), builder: (context, snapshot) {
if (!snapshot.hasData) return const Center(child: CircularProgressIndicator());
var docs = snapshot.data!.docs;
docs.sort((a,b) {
var at = (a.data() as Map)['createdAt'] as Timestamp? ?? Timestamp.now();
var bt = (b.data() as Map)['createdAt'] as Timestamp? ?? Timestamp.now();
return bt.compareTo(at);
});
return ListView(padding: const EdgeInsets.all(20), children: docs.map((doc) {
var d = doc.data() as Map<String, dynamic>;
return ListTile(title: Text(doc.id, style: const TextStyle(letterSpacing: 4, fontWeight: FontWeight.bold, color: Colors.white)), trailing: (d['isUsed'] ?? false) ? const Icon(Icons.check_circle, color: Colors.green) : const Icon(Icons.hourglass_empty, color: Colors.amber));
}).toList());
}))
]);
}
}