Initial Release: SAVEXSTATE Vault V1 - Cyber Orange Edition
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
|
||||
class CommunityView extends StatelessWidget {
|
||||
const CommunityView({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final user = FirebaseAuth.instance.currentUser;
|
||||
|
||||
// We check the user's role at the top level of the Community tab
|
||||
return StreamBuilder<DocumentSnapshot>(
|
||||
stream: FirebaseFirestore.instance.collection('users').doc(user?.uid).snapshots(),
|
||||
builder: (context, userSnapshot) {
|
||||
bool isAdmin = false;
|
||||
if (userSnapshot.hasData && userSnapshot.data!.exists) {
|
||||
final userData = userSnapshot.data!.data() as Map<String, dynamic>;
|
||||
isAdmin = userData['role'] == 'admin';
|
||||
}
|
||||
|
||||
return DefaultTabController(
|
||||
length: 2,
|
||||
child: Column(
|
||||
children: [
|
||||
const TabBar(
|
||||
indicatorColor: Colors.blueAccent,
|
||||
labelColor: Colors.blueAccent,
|
||||
unselectedLabelColor: Colors.grey,
|
||||
tabs: [
|
||||
Tab(text: "Fan Lounge"),
|
||||
Tab(text: "Q&A"),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
children: [
|
||||
_ChatRoom(),
|
||||
_QnAFlow(isAdmin: isAdmin), // Pass admin status here
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --- PART 1: THE CHATROOM ---
|
||||
class _ChatRoom extends StatefulWidget {
|
||||
@override
|
||||
State<_ChatRoom> createState() => _ChatRoomState();
|
||||
}
|
||||
|
||||
class _ChatRoomState extends State<_ChatRoom> {
|
||||
final _msgController = TextEditingController();
|
||||
|
||||
void _sendMessage() async {
|
||||
if (_msgController.text.trim().isEmpty) return;
|
||||
final user = FirebaseAuth.instance.currentUser;
|
||||
|
||||
// Before sending, we try to get the most recent name from Firestore
|
||||
final userDoc = await FirebaseFirestore.instance.collection('users').doc(user?.uid).get();
|
||||
final String name = userDoc.exists ? (userDoc.data()?['displayName'] ?? "Fan") : "Anonymous Fan";
|
||||
|
||||
await FirebaseFirestore.instance.collection('chat').add({
|
||||
'text': _msgController.text.trim(),
|
||||
'senderId': user?.uid,
|
||||
'senderName': name,
|
||||
'timestamp': FieldValue.serverTimestamp(),
|
||||
});
|
||||
_msgController.clear();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: StreamBuilder<QuerySnapshot>(
|
||||
stream: FirebaseFirestore.instance.collection('chat').orderBy('timestamp', descending: true).snapshots(),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) return const Center(child: CircularProgressIndicator());
|
||||
final msgs = snapshot.data!.docs;
|
||||
|
||||
return ListView.builder(
|
||||
reverse: true, // Newest messages at bottom
|
||||
padding: const EdgeInsets.all(10),
|
||||
itemCount: msgs.length,
|
||||
itemBuilder: (context, index) {
|
||||
final data = msgs[index].data() as Map<String, dynamic>;
|
||||
final isMe = data['senderId'] == FirebaseAuth.instance.currentUser?.uid;
|
||||
|
||||
return Align(
|
||||
alignment: isMe ? Alignment.centerRight : Alignment.centerLeft,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 4),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: isMe ? Colors.blueAccent : Colors.grey[850],
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: isMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (!isMe) Text(data['senderName'] ?? "Fan", style: const TextStyle(color: Colors.blueAccent, fontSize: 10, fontWeight: FontWeight.bold)),
|
||||
Text(data['text'] ?? "", style: const TextStyle(color: Colors.white, fontSize: 15)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
color: Colors.black,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _msgController,
|
||||
decoration: const InputDecoration(hintText: "Say something...", border: InputBorder.none)
|
||||
)
|
||||
),
|
||||
IconButton(onPressed: _sendMessage, icon: const Icon(Icons.send, color: Colors.blueAccent)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --- PART 2: THE Q&A ---
|
||||
class _QnAFlow extends StatelessWidget {
|
||||
final bool isAdmin;
|
||||
_QnAFlow({required this.isAdmin});
|
||||
|
||||
final _questionController = TextEditingController();
|
||||
|
||||
void _submitQuestion() async {
|
||||
if (_questionController.text.isEmpty) return;
|
||||
final user = FirebaseAuth.instance.currentUser;
|
||||
final userDoc = await FirebaseFirestore.instance.collection('users').doc(user?.uid).get();
|
||||
final String name = userDoc.exists ? (userDoc.data()?['displayName'] ?? "Fan") : "Fan";
|
||||
|
||||
await FirebaseFirestore.instance.collection('qna').add({
|
||||
'question': _questionController.text.trim(),
|
||||
'answer': null,
|
||||
'senderName': name,
|
||||
'timestamp': FieldValue.serverTimestamp(),
|
||||
});
|
||||
_questionController.clear();
|
||||
}
|
||||
|
||||
// ONLY Admin sees this dialog
|
||||
void _showAnswerDialog(BuildContext context, String qId, String questionText) {
|
||||
final ansController = TextEditingController();
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: Colors.grey[900],
|
||||
title: Text("Answer Question", style: TextStyle(color: Colors.blueAccent[100], fontSize: 16)),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('"$questionText"', style: const TextStyle(fontStyle: FontStyle.italic, color: Colors.white70)),
|
||||
const SizedBox(height: 15),
|
||||
TextField(
|
||||
controller: ansController,
|
||||
maxLines: 3,
|
||||
decoration: const InputDecoration(hintText: "Type your answer...", border: OutlineInputBorder()),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(context), child: const Text("Cancel")),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
await FirebaseFirestore.instance.collection('qna').doc(qId).update({
|
||||
'answer': ansController.text.trim(),
|
||||
});
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text("Post Answer"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: TextField(
|
||||
controller: _questionController,
|
||||
decoration: InputDecoration(
|
||||
hintText: "Ask Sage a question...",
|
||||
filled: true,
|
||||
fillColor: Colors.grey[900],
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none),
|
||||
suffixIcon: IconButton(onPressed: _submitQuestion, icon: const Icon(Icons.help_outline, color: Colors.blueAccent)),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: StreamBuilder<QuerySnapshot>(
|
||||
stream: FirebaseFirestore.instance.collection('qna').orderBy('timestamp', descending: true).snapshots(),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) return const Center(child: CircularProgressIndicator());
|
||||
final questions = snapshot.data!.docs;
|
||||
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
itemCount: questions.length,
|
||||
itemBuilder: (context, index) {
|
||||
final data = questions[index].data() as Map<String, dynamic>;
|
||||
final String qId = questions[index].id;
|
||||
final hasAnswer = data['answer'] != null;
|
||||
|
||||
return Card(
|
||||
color: hasAnswer ? Colors.blueGrey[900] : Colors.grey[850],
|
||||
margin: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: ListTile(
|
||||
title: Text(data['question'] ?? "", style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
subtitle: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
hasAnswer ? "SAGE: ${data['answer']}" : "Waiting for answer...",
|
||||
style: TextStyle(
|
||||
color: hasAnswer ? Colors.greenAccent : Colors.grey,
|
||||
fontWeight: hasAnswer ? FontWeight.bold : FontWeight.normal
|
||||
),
|
||||
),
|
||||
),
|
||||
trailing: isAdmin ? const Icon(Icons.edit, color: Colors.blueAccent, size: 18) : null,
|
||||
onTap: () {
|
||||
if (isAdmin) {
|
||||
_showAnswerDialog(context, qId, data['question']);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user