import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:mqtt_client/mqtt_client.dart'; import '../services/mqtt_service.dart'; import '../services/supabase_service.dart'; class MonitoringScreen extends StatefulWidget { const MonitoringScreen({super.key}); @override State createState() => _MonitoringScreenState(); } class _MonitoringScreenState extends State { bool _isInitialLoading = true; @override void initState() { super.initState(); _initData(); } Future _initData() async { setState(() => _isInitialLoading = true); await _loadLimits(); if (mounted) setState(() => _isInitialLoading = false); } Future _loadLimits() async { try { final data = await SupabaseService().getBatasSensor(); if (data != null && mounted) { Provider.of(context, listen: false).setLimits( double.parse(data['suhu_max'].toString()), double.parse(data['rh_max'].toString()), ); } } catch (e) { debugPrint('Error loading limits: $e'); } } @override Widget build(BuildContext context) { final mqtt = Provider.of(context); final curTemp = double.tryParse(mqtt.suhu) ?? 0; final curHum = double.tryParse(mqtt.kelembapan) ?? 0; return Scaffold( backgroundColor: Colors.black, body: SafeArea( child: _isInitialLoading ? const Center( child: CircularProgressIndicator(color: Colors.green)) : RefreshIndicator( onRefresh: _initData, color: Colors.green, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: Padding( padding: const EdgeInsets.all(20.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // ── Header ── Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Monitoring', style: TextStyle( color: Colors.white, fontSize: 32, fontWeight: FontWeight.bold), ), const Text( 'Sistem Pengeringan Kopi', style: TextStyle( color: Colors.grey, fontSize: 16), ), const SizedBox(height: 5), Row( children: [ Container( width: 8, height: 8, decoration: BoxDecoration( shape: BoxShape.circle, color: mqtt.client?.connectionStatus ?.state == MqttConnectionState.connected ? Colors.green : Colors.red, ), ), const SizedBox(width: 8), Text( mqtt.client?.connectionStatus?.state == MqttConnectionState.connected ? 'Connected' : 'Disconnected', style: TextStyle( color: mqtt.client?.connectionStatus ?.state == MqttConnectionState.connected ? Colors.green : Colors.red, fontSize: 12, ), ), ], ), ], ), const CircleAvatar( backgroundColor: Color(0xFF1A1A1A), radius: 25, child: Icon(Icons.person, color: Colors.green), ), ], ), const SizedBox(height: 10), // ── Timestamp ── Text( 'Update: ${mqtt.timestamp}', style: const TextStyle( color: Colors.grey, fontSize: 11), ), const SizedBox(height: 20), // ── Mode Badge ── Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6), decoration: BoxDecoration( color: mqtt.mode == 'manual' ? Colors.orange.withOpacity(0.2) : Colors.green.withOpacity(0.2), borderRadius: BorderRadius.circular(20), border: Border.all( color: mqtt.mode == 'manual' ? Colors.orange : Colors.green, ), ), child: Text( 'Mode: ${mqtt.mode.toUpperCase()}', style: TextStyle( color: mqtt.mode == 'manual' ? Colors.orange : Colors.green, fontSize: 12, fontWeight: FontWeight.bold, ), ), ), const SizedBox(height: 20), // ── Grid Sensor + Kontrol ── GridView.count( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), crossAxisCount: 2, crossAxisSpacing: 15, mainAxisSpacing: 15, children: [ _buildSensorCard( 'Suhu', '${mqtt.suhu}°C', Icons.thermostat, curTemp > mqtt.maxSuhu ? Colors.red : Colors.redAccent, isAlert: curTemp > mqtt.maxSuhu, onTap: () => Navigator.pushNamed( context, '/analytics', arguments: { 'title': 'Grafik Suhu (°C)', 'color': Colors.redAccent, 'value': '${mqtt.suhu}°C', }, ), ), _buildSensorCard( 'Kelembaban', '${mqtt.kelembapan}%', Icons.water_drop, curHum > mqtt.maxRh ? Colors.red : Colors.cyan, isAlert: curHum > mqtt.maxRh, onTap: () => Navigator.pushNamed( context, '/analytics', arguments: { 'title': 'Grafik Kelembaban (%)', 'color': Colors.cyan, 'value': '${mqtt.kelembapan}%', }, ), ), _buildSensorCard( 'Cahaya', '${mqtt.intensitas} Lux', Icons.wb_sunny, Colors.orange, onTap: () => Navigator.pushNamed( context, '/analytics', arguments: { 'title': 'Grafik Cahaya (Lux)', 'color': Colors.orange, 'value': '${mqtt.intensitas} Lux', }, ), ), _buildControlCard( 'Kipas 1 (Exhaust)', mqtt.isKipas1On, (val) async { mqtt.perintahKipas("1", val); await SupabaseService().logFanAction( val ? 'ON' : 'OFF', mqtt.isKipas2On ? 'ON' : 'OFF', curTemp, curHum, double.tryParse(mqtt.intensitas) ?? 0, ); }, mqtt, ), _buildControlCard( 'Kipas 2 (Intake)', mqtt.isKipas2On, (val) async { mqtt.perintahKipas("2", val); await SupabaseService().logFanAction( mqtt.isKipas1On ? 'ON' : 'OFF', val ? 'ON' : 'OFF', curTemp, curHum, double.tryParse(mqtt.intensitas) ?? 0, ); }, mqtt, ), ], ), // ── Alert Banner ── if (curTemp > mqtt.maxSuhu || curHum > mqtt.maxRh) Container( margin: const EdgeInsets.only(top: 20), padding: const EdgeInsets.all(15), decoration: BoxDecoration( color: Colors.red.withOpacity(0.2), borderRadius: BorderRadius.circular(15), border: Border.all(color: Colors.red), ), child: const Row( children: [ Icon(Icons.warning_amber_rounded, color: Colors.red), SizedBox(width: 10), Expanded( child: Text( 'Peringatan: Parameter pengeringan melewati batas aman!', style: TextStyle( color: Colors.red, fontWeight: FontWeight.bold, fontSize: 12), ), ), ], ), ), const SizedBox(height: 20), ], ), ), ), ), ), ); } Widget _buildSensorCard( String title, String value, IconData icon, Color color, { bool isAlert = false, VoidCallback? onTap, }) { return GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.all(15), decoration: BoxDecoration( color: isAlert ? Colors.red.withOpacity(0.1) : const Color(0xFF1A1A1A), borderRadius: BorderRadius.circular(20), border: isAlert ? Border.all(color: Colors.red, width: 2) : null, ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, color: color, size: 40), const SizedBox(height: 10), Text(title, style: const TextStyle(color: Colors.grey, fontSize: 14)), const SizedBox(height: 5), Text(value, style: const TextStyle( color: Colors.white, fontSize: 22, fontWeight: FontWeight.bold)), ], ), ), ); } Widget _buildControlCard( String title, bool isOn, Function(bool) onChanged, MqttService mqtt, ) { return Container( padding: const EdgeInsets.all(15), decoration: BoxDecoration( color: const Color(0xFF1A1A1A), borderRadius: BorderRadius.circular(20), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.settings_input_component, color: Colors.green, size: 20), const SizedBox(width: 8), Expanded( child: Text(title, style: const TextStyle( color: Colors.white, fontSize: 13, fontWeight: FontWeight.bold)), ), ], ), const Spacer(), Text( 'Mode: ${mqtt.mode.toUpperCase()}', style: const TextStyle( color: Colors.green, fontSize: 9, fontWeight: FontWeight.bold), ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(isOn ? 'ON' : 'OFF', style: const TextStyle( color: Colors.grey, fontSize: 12)), SizedBox( height: 30, child: Switch( value: isOn, onChanged: mqtt.mode == 'manual' ? onChanged : null, activeColor: Colors.green, ), ), ], ), ], ), ); } }