TKK_E32230148/lib/pages/home_dashboard_page.dart

561 lines
20 KiB
Dart

import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import '../register_page.dart';
import '../widgets/ui_helpers.dart';
import 'cards_page.dart';
import 'reminder_page.dart';
import 'profile_page.dart';
import 'history_page.dart';
import '../login_page.dart';
class HomeDashboardPage extends StatefulWidget {
const HomeDashboardPage({super.key});
@override
State<HomeDashboardPage> createState() => _HomeDashboardPageState();
}
class _HomeDashboardPageState extends State<HomeDashboardPage> {
final DatabaseReference rtdb = FirebaseDatabase.instance.ref();
final FirebaseFirestore firestore = FirebaseFirestore.instance;
final FlutterLocalNotificationsPlugin notif = FlutterLocalNotificationsPlugin();
String lastUID = "-";
String statusAkses = "-";
String namaUser = "User";
String emailUser = "-";
String roleUser = "user";
String relayStatus = "OFF";
String doorSensor = "CLOSED";
int reminderHour = 21;
int reminderMinute = 0;
bool reminderEnabled = true;
bool modeTambah = false;
String namaKartuBaru = "";
int selectedIndex = 0;
String _lastRfidUid = "";
String _lastRfidStatus = "";
bool _hasRfidSnapshot = false;
String _lastLogKey = "";
DateTime? _lastLogTime;
@override
void initState() {
super.initState();
initNotification();
ambilNama();
listenRfidSistem();
listenRelay();
listenDoorSensor();
listenNotification();
listenReminder();
}
void initNotification() async {
const AndroidInitializationSettings android = AndroidInitializationSettings('@mipmap/ic_launcher');
const InitializationSettings settings = InitializationSettings(android: android);
await notif.initialize(settings: settings);
}
Future<void> showNotif(String title, String body) async {
int notifId = DateTime.now().millisecondsSinceEpoch ~/ 1000;
const AndroidNotificationDetails androidDetails = AndroidNotificationDetails(
"door_security",
"Door Security",
importance: Importance.max,
priority: Priority.high,
playSound: true,
enableVibration: true,
);
const NotificationDetails details = NotificationDetails(android: androidDetails);
await notif.show(
id: notifId,
title: title,
body: body,
notificationDetails: details,
);
}
Future<void> simpanRiwayatAkses(String uid, String status) async {
if (uid == "-" || uid.isEmpty || uid == "IDLE" || status == "IDLE") return;
final String logKey = '$uid|$status';
final now = DateTime.now();
if (_lastLogKey == logKey && _lastLogTime != null && now.difference(_lastLogTime!).inSeconds < 5) {
debugPrint('Duplicate log skipped: $logKey');
return;
}
String namaKartu = "Unregistered Card";
if (uid == "app") {
namaKartu = namaUser;
} else if (_isGrantedStatus(status)) {
namaKartu = "Registered Card (No Name)";
try {
final cardDoc = await firestore.collection("cards").doc(uid).get();
if (cardDoc.exists && cardDoc.data() != null) {
namaKartu = cardDoc.data()!["name"] ?? "No Name";
}
} catch (e) {
debugPrint("Gagal fetch nama kartu: $e");
}
}
try {
await firestore.collection("access_logs").add({
"uid": uid,
"name": namaKartu,
"status": status,
"timestamp": FieldValue.serverTimestamp(),
});
_lastLogKey = logKey;
_lastLogTime = now;
debugPrint("Log Sukses Tersimpan di Firestore.");
} catch (e) {
debugPrint("Gagal menulis ke Firestore: $e");
}
}
bool _isGrantedStatus(String status) {
final s = status.toString().toUpperCase();
return s.contains('GRANT') || s.contains('SUCCESS') || s.contains('ACCEPT') || s.contains('OK');
}
void listenNotification() {
rtdb.child("rfid/notification").onValue.listen((event) async {
if (event.snapshot.value == null) return;
String notifData = event.snapshot.value.toString();
if (notifData == "ACCESS_DENIED") {
await showNotif("ACCESS DENIED", "RFID card tidak terdaftar");
await simpanRiwayatAkses(lastUID, "ACCESS_DENIED");
await rtdb.child("rfid/notification").set("IDLE");
}
});
}
void listenReminder() {
rtdb.child("rfid/reminder").onValue.listen((event) {
if (event.snapshot.value == null) return;
Map data = Map.from(event.snapshot.value as Map);
if (mounted) {
setState(() {
reminderEnabled = data["enabled"] ?? true;
reminderHour = data["hour"] ?? 21;
reminderMinute = data["minute"] ?? 0;
});
}
});
}
Future setReminderTime() async {
TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: TimeOfDay(hour: reminderHour, minute: reminderMinute),
);
if (picked != null) {
await rtdb.child("rfid/reminder").set({
"enabled": true,
"hour": picked.hour,
"minute": picked.minute,
});
}
}
Future ambilNama() async {
final user = FirebaseAuth.instance.currentUser;
if (user == null) return;
emailUser = user.email ?? "-";
final doc = await firestore.collection("users").doc(user.uid).get();
if (doc.exists && doc.data() != null) {
if (mounted) {
setState(() {
namaUser = doc.data()!["name"] ?? "User";
roleUser = (doc.data()!["role"] ?? "user").toString();
});
}
}
}
void listenRfidSistem() {
rtdb.child("rfid").onValue.listen((event) async {
if (event.snapshot.value == null) return;
final Map<dynamic, dynamic> rfidData = Map.from(event.snapshot.value as Map);
String uidTerkini = (rfidData["lastScan"] ?? "-").toString();
String statusTerkini = (rfidData["status"] ?? "-").toString();
if (uidTerkini == "IDLE" || statusTerkini == "IDLE" || uidTerkini == "-") {
return;
}
if (!_hasRfidSnapshot) {
_lastRfidUid = uidTerkini;
_lastRfidStatus = statusTerkini;
_hasRfidSnapshot = true;
}
if (uidTerkini == _lastRfidUid && statusTerkini == _lastRfidStatus) {
if (mounted) {
setState(() {
lastUID = uidTerkini;
statusAkses = statusTerkini;
});
}
return;
}
if (mounted) {
setState(() {
lastUID = uidTerkini;
statusAkses = statusTerkini;
});
}
if (modeTambah) {
await firestore.collection("cards").doc(uidTerkini).set({
"name": namaKartuBaru,
"active": true,
"createdAt": FieldValue.serverTimestamp()
});
await rtdb.child("rfid/cards/$uidTerkini").set({
"name": namaKartuBaru,
"active": true,
});
if (mounted) {
setState(() { modeTambah = false; });
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Card successfully added")),
);
}
return;
}
if (statusTerkini == "ACCESS_GRANTED" || statusTerkini == "SUCCESS") {
final String uidLog = uidTerkini;
final String statusLog = statusTerkini;
await simpanRiwayatAkses(uidLog, statusLog);
Future.delayed(const Duration(seconds: 3), () {
rtdb.child("rfid/status").set("IDLE");
rtdb.child("rfid/lastScan").set("IDLE");
});
}
});
}
void listenRelay() {
rtdb.child("rfid/relay").onValue.listen((event) {
if (event.snapshot.value != null && mounted) {
setState(() { relayStatus = event.snapshot.value.toString(); });
}
});
}
void listenDoorSensor() {
rtdb.child("rfid/doorSensor").onValue.listen((event) {
if (event.snapshot.value != null && mounted) {
setState(() { doorSensor = event.snapshot.value.toString(); });
}
});
}
Future<void> bukaPintu() async {
await rtdb.child("rfid/doorControl").set("OPEN");
await simpanRiwayatAkses("app", "APP_OPEN");
}
Future<void> tutupPintu() async {
await rtdb.child("rfid/doorControl").set("CLOSE");
await simpanRiwayatAkses("app", "APP_CLOSE");
}
Future hapusKartu(String uid) async {
await firestore.collection("cards").doc(uid).delete();
await rtdb.child("rfid/cards/$uid").remove();
}
bool get isAdmin => roleUser.toLowerCase() == 'admin';
Future<void> openCreateUserPage() async {
if (!isAdmin) return;
await Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const RegisterPage(role: 'user', signOutAfterRegister: false),
),
);
}
Future logout() async {
await FirebaseAuth.instance.signOut();
if (mounted) {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => const LoginPage()),
(route) => false,
);
}
}
Future<void> tambahKartuDialog() async {
if (!isAdmin) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Access denied. Only admin can add cards."), backgroundColor: Colors.redAccent),
);
return;
}
final snapshot = await firestore.collection("cards").get();
if (snapshot.docs.length >= 10) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Cannot add card. Maximum limit reached!"), backgroundColor: Colors.redAccent),
);
return;
}
TextEditingController controller = TextEditingController();
if (!mounted) return;
showDialog(
context: context,
builder: (_) => AlertDialog(
backgroundColor: const Color(0xff111111),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25)),
title: const Text("Add Card", style: TextStyle(color: Colors.white)),
content: TextField(
controller: controller,
style: const TextStyle(color: Colors.white),
decoration: InputDecoration(
hintText: "Card Name",
hintStyle: const TextStyle(color: Colors.white38),
filled: true,
fillColor: Colors.black,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(18), borderSide: BorderSide.none),
),
),
actions: [
TextButton(onPressed: () => Navigator.pop(context), child: const Text("Cancel")),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blueAccent,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
),
onPressed: () {
if (controller.text.isNotEmpty) {
namaKartuBaru = controller.text;
setState(() { modeTambah = true; });
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Please scan RFID card")),
);
}
},
child: const Text("Scan"),
)
],
),
);
}
@override
Widget build(BuildContext context) {
bool relayOn = relayStatus == "ON";
bool doorOpen = doorSensor == "OPEN";
return AnnotatedRegion<SystemUiOverlayStyle>(
value: const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.light,
),
child: Scaffold(
backgroundColor: Colors.black,
floatingActionButton: selectedIndex == 1 && isAdmin
? FloatingActionButton.extended(
backgroundColor: Colors.blueAccent,
elevation: 10,
onPressed: tambahKartuDialog,
icon: const Icon(Icons.add),
label: const Text("Add Card"),
)
: null,
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), // Dipersempit agar pas di Pova 5
child: IndexedStack(
index: selectedIndex,
children: [
// TAB 0: DASHBOARD (UKURAN LEBIH COMPACT)
SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
modernHeader(),
const SizedBox(height: 20), // Jarak dikurangi
ModernCard(
child: Column(
children: [
Container(
height: 100, width: 100, // Diperkecil dari 130 ke 100
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: doorOpen ? [Colors.greenAccent, Colors.green] : [Colors.redAccent, Colors.red],
),
boxShadow: [
BoxShadow(
color: doorOpen ? Colors.green.withOpacity(0.4) : Colors.red.withOpacity(0.4),
blurRadius: 18,
),
],
),
child: Icon(
doorOpen ? Icons.lock_open_rounded : Icons.lock_rounded,
color: Colors.white, size: 50, // Ikon diperkecil dari 70 ke 50
),
),
const SizedBox(height: 15),
Text(
doorOpen ? 'DOOR OPEN' : 'DOOR LOCKED',
style: const TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold), // Font diperkecil
),
const SizedBox(height: 6),
Text(
relayOn ? 'Security system active' : 'Security system standby',
style: const TextStyle(color: Colors.white54, fontSize: 13),
),
const SizedBox(height: 15),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), // Padding switch diperkecil
decoration: BoxDecoration(color: Colors.black, borderRadius: BorderRadius.circular(50)),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Door Control', style: TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w600)),
const SizedBox(width: 10),
Transform.scale(
scale: 0.85, // Switch diperkecil sedikit agar presisi
child: Switch(
value: relayOn,
activeColor: Colors.greenAccent,
onChanged: (value) { value ? bukaPintu() : tutupPintu(); },
),
),
],
),
),
],
),
),
const SizedBox(height: 15),
ModernCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(Icons.history, color: Colors.white, size: 20),
SizedBox(width: 8),
Text('LAST SCAN STATE', style: TextStyle(color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)),
],
),
const SizedBox(height: 15),
InfoTile(icon: Icons.badge_rounded, title: 'UID CARD', value: lastUID),
const SizedBox(height: 10),
InfoTile(icon: Icons.security_rounded, title: 'STATUS', value: statusAkses),
const SizedBox(height: 10),
InfoTile(
icon: Icons.access_time_rounded,
title: 'TIME',
value: '${DateTime.now().day.toString().padLeft(2, '0')}/${DateTime.now().month.toString().padLeft(2, '0')}/${DateTime.now().year} ${TimeOfDay.now().format(context)}',
),
],
),
),
],
),
),
CardsPage(firestore: firestore, onDeleteCard: hapusKartu, canEditCards: isAdmin),
HistoryPage(firestore: firestore, canClearHistory: isAdmin),
ReminderPage(
reminderEnabled: reminderEnabled,
reminderHour: reminderHour,
reminderMinute: reminderMinute,
onSetReminderTime: setReminderTime,
onToggleReminder: (value) { rtdb.child('rfid/reminder/enabled').set(value); },
),
ProfilePage(
namaUser: namaUser, emailUser: emailUser, role: roleUser,
onLogout: logout, onCreateUser: isAdmin ? openCreateUserPage : null,
),
],
),
),
),
bottomNavigationBar: Container(
margin: const EdgeInsets.only(left: 12, right: 12, bottom: 12, top: 4), // Dikurangi margin bawahnya
decoration: BoxDecoration(
color: const Color(0xff111111),
borderRadius: BorderRadius.circular(24),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.4), blurRadius: 15)],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(24),
child: BottomNavigationBar(
currentIndex: selectedIndex,
backgroundColor: Colors.transparent,
elevation: 0,
selectedItemColor: Colors.blueAccent,
unselectedItemColor: Colors.white38,
type: BottomNavigationBarType.fixed,
iconSize: 22, // Ukuran ikon navigasi diturunkan sedikit agar proporsional
selectedFontSize: 11,
unselectedFontSize: 11,
onTap: (index) { setState(() { selectedIndex = index; }); },
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home_rounded), label: "Home"),
BottomNavigationBarItem(icon: Icon(Icons.credit_card_rounded), label: "Cards"),
BottomNavigationBarItem(icon: Icon(Icons.manage_search_rounded), label: "History"),
BottomNavigationBarItem(icon: Icon(Icons.notifications_active_rounded), label: "Reminder"),
BottomNavigationBarItem(icon: Icon(Icons.person_rounded), label: "Profile"),
],
),
),
),
),
);
}
Widget modernHeader() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('DOORGUARD', style: TextStyle(color: Colors.white, fontSize: 26, fontWeight: FontWeight.bold, letterSpacing: 0.5)), // Ukuran font disesuaikan
SizedBox(height: 4),
Text('Smart RFID Door Monitoring System', style: TextStyle(color: Colors.white54, fontSize: 13)),
],
),
GestureDetector(
onTap: () { setState(() { selectedIndex = 4; }); },
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(color: const Color(0xff111111), borderRadius: BorderRadius.circular(15)),
child: const Icon(Icons.person, color: Colors.white, size: 22),
),
),
],
);
}
}