import 'dart:async'; import 'package:flutter/material.dart'; import 'package:firebase_database/firebase_database.dart'; import 'package:intl/intl.dart'; class MonitoringPage extends StatefulWidget { const MonitoringPage({super.key}); @override _MonitoringPageState createState() => _MonitoringPageState(); } class _MonitoringPageState extends State { // Referensi ke node 'monitoring' untuk data sensor dan status aktual perangkat final dbMonitoringRef = FirebaseDatabase.instance.ref("monitoring"); // Referensi ke node 'kontrol' untuk perintah manual final dbControlRef = FirebaseDatabase.instance.ref("kontrol"); // Referensi ke node 'histori' untuk log aktivitas final historiRef = FirebaseDatabase.instance.ref("histori"); // Variabel untuk data sensor double ph = 0, tds = 0, suhu = 0; // Variabel status aktual perangkat (dari /monitoring) bool fanStatus = false; bool pompaUtamaStatus = false; bool abmixHabis = false; // Status sensor level air AB Mix bool pompaAbMixStatus = false; // Status aktual ON/OFF pompa AB Mix (baru dari Arduino) // Variabel kontrol mode manual (dari /kontrol) bool manualMode = false; String waktu = DateFormat('HH:mm:ss').format(DateTime.now()); late Timer _timer; @override void initState() { super.initState(); // Timer untuk update waktu lokal setiap detik _timer = Timer.periodic(const Duration(seconds: 1), (_) { setState(() { waktu = DateFormat('HH:mm:ss').format(DateTime.now()); }); }); // --- LOGIKA INISIALISASI NODE /KONTROL --- dbControlRef .once() .then((DatabaseEvent event) { if (!event.snapshot.exists) { dbControlRef .set({ "mode_manual": false, "pompa": false, "fan": false, "abmix": false, }) .then((_) { _showSnackBar( "Node /kontrol berhasil diinisialisasi di Firebase.", ); }) .catchError((e) { _showSnackBar("Gagal menginisialisasi node /kontrol: $e"); }); } }) .catchError((e) { _showSnackBar("Gagal memeriksa keberadaan node /kontrol: $e"); }); // --- AKHIR LOGIKA INISIALISASI --- // Listener untuk data monitoring (sensor dan status aktual perangkat dari Arduino) dbMonitoringRef.onValue.listen((event) { if (event.snapshot.value != null) { final data = Map.from(event.snapshot.value as Map); final bool oldAbmixHabis = abmixHabis; setState(() { ph = (data['ph'] ?? 0).toDouble(); tds = (data['tds'] ?? 0).toDouble(); suhu = (data['suhu_udara'] ?? 0).toDouble(); fanStatus = data['fan'] ?? false; pompaUtamaStatus = data['pompa'] ?? false; abmixHabis = data['abmix_habis'] ?? false; pompaAbMixStatus = data['pompa_abmix'] ?? false; }); if (oldAbmixHabis != abmixHabis && abmixHabis) { _showSnackBar("⚠️ AB MIX HABIS!"); } } }); // Listener untuk data kontrol (mode manual dari Firebase) dbControlRef.onValue.listen((event) { if (event.snapshot.value != null) { final dataControl = Map.from( event.snapshot.value as Map, ); setState(() { manualMode = dataControl['mode_manual'] ?? false; }); } }); } @override void dispose() { _timer.cancel(); // Hentikan timer di sini super.dispose(); } // Fungsi untuk menampilkan SnackBar di bagian bawah layar void _showSnackBar(String pesan) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(pesan))); } // Fungsi untuk mencatat histori ke Firebase dari Flutter (untuk perintah manual) void _logHistoriManual(String aksi) { historiRef .push() .set({ "timestamp": DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now()), "aksi": aksi, }) .catchError((e) { _showSnackBar("❌ Gagal mencatat histori manual: $e"); }); } // Fungsi untuk mengontrol perangkat (hanya dalam mode manual) void toggleDevice(String keyFirebase, bool currentStatus) { final newValue = !currentStatus; String label = ""; String statusText = newValue ? "ON" : "OFF"; if (keyFirebase == "fan") { label = "Kipas"; // Optimistic UI update: langsung ubah status di UI setState(() { fanStatus = newValue; }); } else if (keyFirebase == "pompa") { label = "Pompa Utama"; // Optimistic UI update: langsung ubah status di UI setState(() { pompaUtamaStatus = newValue; }); } else if (keyFirebase == "abmix") { label = "Pompa AB Mix"; // Optimistic UI update: langsung ubah status di UI setState(() { pompaAbMixStatus = newValue; }); } dbControlRef .update({keyFirebase: newValue}) .then((_) { _logHistoriManual("$label $statusText (Perintah Manual)"); _showSnackBar("✅ Perintah $label berhasil dikirim: $statusText"); }) .catchError((e) { _showSnackBar("❌ Gagal mengirim perintah $label: $e"); // Revert UI jika ada error setState(() { if (keyFirebase == "fan") fanStatus = !newValue; else if (keyFirebase == "pompa") pompaUtamaStatus = !newValue; else if (keyFirebase == "abmix") pompaAbMixStatus = !newValue; }); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Monitoring Selada', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white, ), ), Text( 'Jam: $waktu', style: TextStyle(fontSize: 14, color: Colors.white70), ), ], ), centerTitle: false, backgroundColor: Colors.green[700], elevation: 0, ), body: monitoringContent(), ); } // Widget utama untuk konten halaman monitoring Widget monitoringContent() { return Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.green.shade50, Colors.green.shade100], ), ), child: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.all(20.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Bagian Judul dan Waktu sebelumnya sudah di AppBar, jadi ini dihapus // const SizedBox(height: 25), // Jarak ini juga bisa dikurangi atau dihapus // Bagian Informasi Sensor Text( "Data Sensor", style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.green[800], ), ), const SizedBox(height: 15), infoCard("pH", ph.toStringAsFixed(2), Icons.science), infoCard( "TDS", "${tds.toStringAsFixed(0)} ppm", Icons.water_drop, ), infoCard( "Suhu Udara", "${suhu.toStringAsFixed(1)} °C", Icons.thermostat, ), const SizedBox(height: 25), // Bagian Kontrol Sistem Text( "Kontrol Sistem", style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.green[800], ), ), const SizedBox(height: 15), // Switch untuk Mode Manual switchCard("Mode Manual", manualMode, (newValue) { // Optimistic UI update untuk mode manual setState(() { manualMode = newValue; }); dbControlRef .update({"mode_manual": newValue}) .then((_) { _logHistoriManual( "Mode Manual ${newValue ? 'ON' : 'OFF'}", ); }) .catchError((e) { _showSnackBar("Gagal mengubah mode manual: $e"); // Revert UI jika ada error setState(() { manualMode = !newValue; }); }); }), // Switch untuk Kipas (hanya bisa diubah jika manualMode aktif) switchCard( "Kipas", fanStatus, manualMode ? (newValue) => toggleDevice("fan", fanStatus) : null, ), // Switch untuk Pompa Utama (hanya bisa diubah jika manualMode aktif) switchCard( "Pompa Utama", pompaUtamaStatus, manualMode ? (newValue) => toggleDevice("pompa", pompaUtamaStatus) : null, ), // Switch untuk Pompa AB Mix (hanya bisa diubah jika manualMode aktif) switchCard( "Pompa AB Mix", pompaAbMixStatus, manualMode ? (newValue) => toggleDevice("abmix", pompaAbMixStatus) : null, ), const SizedBox(height: 15), // Pesan jika mode manual tidak aktif if (!manualMode) Container( padding: const EdgeInsets.all(12.0), decoration: BoxDecoration( color: Colors.orange.shade100, borderRadius: BorderRadius.circular(10), border: Border.all(color: Colors.orange.shade300), ), child: Row( children: [ Icon(Icons.lock, color: Colors.orange[700]), const SizedBox(width: 8), Expanded( child: Text( "Aktifkan Mode Manual untuk kontrol perangkat", style: TextStyle( color: Colors.orange[700], fontSize: 14, ), ), ), ], ), ), const SizedBox(height: 10), // Pesan peringatan AB MIX HABIS if (abmixHabis) Container( padding: const EdgeInsets.all(12.0), decoration: BoxDecoration( color: Colors.red.shade100, borderRadius: BorderRadius.circular(10), border: Border.all(color: Colors.red.shade300), ), child: Row( children: [ Icon(Icons.warning, color: Colors.red[700]), const SizedBox(width: 8), Expanded( child: Text( "⚠️ AB MIX HABIS! Segera isi ulang.", style: TextStyle( color: Colors.red[700], fontWeight: FontWeight.bold, fontSize: 14, ), ), ), ], ), ), ], ), ), ), ); } // Widget kustom untuk menampilkan informasi sensor dalam bentuk kartu yang lebih menarik Widget infoCard(String label, String value, IconData icon) { return Card( margin: const EdgeInsets.symmetric(vertical: 8.0), elevation: 4, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), child: Padding( padding: const EdgeInsets.all(16.0), child: Row( children: [ Icon(icon, color: Colors.green[600], size: 35), const SizedBox(width: 15), Expanded( child: Text( label, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w500, color: Colors.grey[800], ), ), ), Text( value, style: TextStyle( fontWeight: FontWeight.bold, fontSize: 20, color: Colors.green[800], ), ), ], ), ), ); } // Widget kustom untuk menampilkan switch kontrol perangkat dalam bentuk kartu yang lebih menarik Widget switchCard(String label, bool status, ValueChanged? onChanged) { return Card( margin: const EdgeInsets.symmetric(vertical: 8.0), elevation: 4, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), child: Row( children: [ Expanded( child: Text( label, style: TextStyle( fontSize: 18, fontWeight: FontWeight.w500, color: Colors.grey[800], ), ), ), Switch( value: status, onChanged: onChanged, activeColor: Colors.green[600], inactiveThumbColor: Colors.grey, inactiveTrackColor: Colors.grey[300], ), ], ), ), ); } }