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 createState() => _HomeDashboardPageState(); } class _HomeDashboardPageState extends State { 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 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 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 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 bukaPintu() async { await rtdb.child("rfid/doorControl").set("OPEN"); await simpanRiwayatAkses("app", "APP_OPEN"); } Future 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 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 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( 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), ), ), ], ); } }