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'; import '../constants/app_colors.dart'; // <-- IMPORT APP COLORS DITAMBAHKAN class MonitoringScreen extends StatefulWidget { final VoidCallback? onProfileTap; const MonitoringScreen({super.key, this.onProfileTap}); @override State createState() => _MonitoringScreenState(); } class _MonitoringScreenState extends State { bool _isInitialLoading = true; double _prevSuhu = 0; double _prevKelembapan = 0; double _prevIntensitas = 0; @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()), double.parse(data['suhu_min'].toString()), double.parse(data['rh_min'].toString()), ); } } catch (e) { debugPrint('Error loading limits: $e'); } } @override Widget build(BuildContext context) { final mqtt = Provider.of(context); final curSuhu = double.tryParse(mqtt.suhu) ?? 0; final curHum = double.tryParse(mqtt.kelembapan) ?? 0; final curLux = double.tryParse(mqtt.intensitas) ?? 0; WidgetsBinding.instance.addPostFrameCallback((_) { _prevSuhu = curSuhu; _prevKelembapan = curHum; _prevIntensitas = curLux; }); return Scaffold( // --- BERUBAH: Background Utama --- backgroundColor: AppColors.background, body: SafeArea( child: _isInitialLoading // --- BERUBAH: Warna Loading Indicator --- ? const Center(child: CircularProgressIndicator(color: AppColors.primary)) : RefreshIndicator( onRefresh: _initData, // --- BERUBAH: Warna Refresh Indicator --- color: AppColors.primary, child: LayoutBuilder( builder: (context, constraints) { const headerHeight = 175.0; const gaps = 15.0 * 2; const padding = 20.0 * 2; final available = constraints.maxHeight - headerHeight - gaps - padding; final cardH = (available / 3).clamp(100.0, 180.0); return SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: ConstrainedBox( constraints: BoxConstraints(minHeight: constraints.maxHeight), child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // ── Header ── Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Monitoring', style: TextStyle( // --- BERUBAH: Warna Judul --- color: AppColors.textMain, fontSize: 32, fontWeight: FontWeight.bold)), const Text('Sistem Pengeringan Kopi', // --- BERUBAH: Warna Sub-judul --- style: TextStyle(color: AppColors.textSecondary, fontSize: 16)), const SizedBox(height: 5), Row( children: [ Container( width: 8, height: 8, decoration: BoxDecoration( shape: BoxShape.circle, // --- BERUBAH: Indikator Titik Koneksi --- color: mqtt.client?.connectionStatus?.state == MqttConnectionState.connected ? AppColors.success : AppColors.error, ), ), const SizedBox(width: 8), Text( mqtt.client?.connectionStatus?.state == MqttConnectionState.connected ? 'Connected' : 'Disconnected', style: TextStyle( // --- BERUBAH: Warna Teks Koneksi --- color: mqtt.client?.connectionStatus?.state == MqttConnectionState.connected ? AppColors.success : AppColors.error, fontSize: 12, fontWeight: FontWeight.bold, ), ), ], ), ], ), GestureDetector( onTap: widget.onProfileTap, child: const CircleAvatar( // --- BERUBAH: Latar Belakang Avatar Profil --- backgroundColor: AppColors.cardBg, radius: 25, // --- BERUBAH: Warna Ikon Avatar --- child: Icon(Icons.person, color: AppColors.primary), ), ), ], ), const SizedBox(height: 6), // ── Timestamp ── Text('Update: ${mqtt.timestamp}', // --- BERUBAH: Warna Teks Timestamp --- style: const TextStyle(color: AppColors.textSecondary, fontSize: 11)), const SizedBox(height: 10), // ── Mode Badge ── Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( // --- BERUBAH: Latar Belakang Transparan Badge --- color: mqtt.mode == 'manual' ? AppColors.warning.withValues(alpha: 0.2) : AppColors.success.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(20), border: Border.all( // --- BERUBAH: Warna Garis Pinggir Badge --- color: mqtt.mode == 'manual' ? AppColors.warning : AppColors.success, ), ), child: Text( 'Mode: ${mqtt.mode.toUpperCase()}', style: TextStyle( // --- BERUBAH: Warna Teks Badge --- color: mqtt.mode == 'manual' ? AppColors.warning : AppColors.success, fontSize: 12, fontWeight: FontWeight.bold, ), ), ), const SizedBox(height: 15), // ── Baris 1: Suhu & Kelembaban ── SizedBox( height: cardH, child: Row( children: [ Expanded( child: _buildSensorCard( title: 'Suhu', prev: _prevSuhu, cur: curSuhu, suffix: '°C', dec: 1, icon: Icons.thermostat, // --- BERUBAH: Warna Tema Kartu Suhu --- color: curSuhu > mqtt.maxSuhu ? AppColors.error : AppColors.warning, isAlert: curSuhu > mqtt.maxSuhu, isLow: curSuhu < mqtt.minSuhu, onTap: () => Navigator.pushNamed(context, '/analytics', arguments: {'title': 'Grafik Suhu (°C)', 'color': AppColors.error, 'value': '${mqtt.suhu}°C'}), ), ), const SizedBox(width: 15), Expanded( child: _buildSensorCard( title: 'Kelembaban', prev: _prevKelembapan, cur: curHum, suffix: '%', dec: 1, icon: Icons.water_drop, // --- BERUBAH: Warna Tema Kartu Kelembaban --- color: curHum > mqtt.maxRh ? AppColors.error : AppColors.info, isAlert: curHum > mqtt.maxRh, isLow: curHum < mqtt.minRh, onTap: () => Navigator.pushNamed(context, '/analytics', arguments: {'title': 'Grafik Kelembaban (%)', 'color': AppColors.info, 'value': '${mqtt.kelembapan}%'}), ), ), ], ), ), const SizedBox(height: 15), // ── Baris 2: Cahaya full width ── SizedBox( height: cardH, width: double.infinity, child: _buildSensorCard( title: 'Cahaya', prev: _prevIntensitas, cur: curLux, suffix: ' Lux', dec: 1, icon: Icons.wb_sunny, // --- BERUBAH: Warna Tema Kartu Cahaya --- color: AppColors.warning, onTap: () => Navigator.pushNamed(context, '/analytics', arguments: {'title': 'Grafik Cahaya (Lux)', 'color': AppColors.light, 'value': '${mqtt.intensitas} Lux'}), ), ), const SizedBox(height: 15), // ── Baris 3: Kipas 1 & Kipas 2 ── SizedBox( height: cardH, child: Row( children: [ Expanded( child: _buildControlCard( 'Kipas 1\n(Intake)', mqtt.isKipas1On, (val) => mqtt.perintahKipas("1", val), mqtt, ), ), const SizedBox(width: 15), Expanded( child: _buildControlCard( 'Kipas 2\n(Exhaust)', mqtt.isKipas2On, (val) => mqtt.perintahKipas("2", val), mqtt, ), ), ], ), ), // ── Alert Banner ── if (curSuhu > mqtt.maxSuhu || curHum > mqtt.maxRh) Container( margin: const EdgeInsets.only(top: 15), padding: const EdgeInsets.all(15), decoration: BoxDecoration( // --- BERUBAH: Background Peringatan --- color: AppColors.error.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(15), // --- BERUBAH: Garis Pinggir Peringatan --- border: Border.all(color: AppColors.error), ), child: const Row( children: [ // --- BERUBAH: Ikon Peringatan --- Icon(Icons.warning_amber_rounded, color: AppColors.error), SizedBox(width: 10), Expanded( child: Text( 'Peringatan: Parameter pengeringan melewati batas aman!', style: TextStyle( // --- BERUBAH: Teks Peringatan --- color: AppColors.error, fontWeight: FontWeight.bold, fontSize: 12), ), ), ], ), ), if (curSuhu < mqtt.minSuhu || curHum < mqtt.minRh) Container( margin: const EdgeInsets.only(top: 15), padding: const EdgeInsets.all(15), decoration: BoxDecoration( // --- BERUBAH: Background Info/Low --- color: AppColors.info.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(15), // --- BERUBAH: Garis Pinggir Info/Low --- border: Border.all(color: AppColors.info), ), child: const Row( children: [ // --- BERUBAH: Ikon Info/Low --- Icon(Icons.info_outline, color: AppColors.info), SizedBox(width: 10), Expanded( child: Text( 'Info: Parameter pengeringan di bawah batas minimum!', style: TextStyle( // --- BERUBAH: Teks Info/Low --- color: AppColors.info, fontWeight: FontWeight.bold, fontSize: 12), ), ), ], ), ), ], ), ), ), ); }, ), ), ), ); } Widget _buildSensorCard({ required String title, required double prev, required double cur, required String suffix, required int dec, required IconData icon, required Color color, bool isAlert = false, bool isLow = false, VoidCallback? onTap, }) { return GestureDetector( onTap: onTap, child: Container( width: double.infinity, height: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( // --- BERUBAH: Kartu Latar Putih Bersih atau Transparan Merah/Biru --- color: isAlert ? AppColors.error.withValues(alpha: 0.1) : isLow ? AppColors.info.withValues(alpha: 0.1) : AppColors.cardBg, borderRadius: BorderRadius.circular(20), // --- BERUBAH: Memberi garis halus agar kartu terlihat pop up di latar terang --- border: isAlert ? Border.all(color: AppColors.error, width: 2) : isLow ? Border.all(color: AppColors.info, width: 2) : Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, color: color, size: 32), const SizedBox(height: 6), // --- BERUBAH: Warna Judul Sensor --- Text(title, style: const TextStyle(color: AppColors.textSecondary, fontSize: 12)), const SizedBox(height: 4), TweenAnimationBuilder( tween: Tween(begin: prev, end: cur), duration: const Duration(milliseconds: 800), curve: Curves.easeOutCubic, builder: (context, value, _) => FittedBox( fit: BoxFit.scaleDown, child: Text( '${value.toStringAsFixed(dec)}$suffix', style: const TextStyle( // --- BERUBAH: Warna Angka/Nilai Sensor --- color: AppColors.textMain, fontSize: 22, fontWeight: FontWeight.bold), ), ), ), ], ), ), ); } Widget _buildControlCard( String title, bool isOn, Function(bool) onChanged, MqttService mqtt, ) { return Container( width: double.infinity, height: double.infinity, padding: const EdgeInsets.all(12), decoration: BoxDecoration( // --- BERUBAH: Kartu Latar Putih Bersih & Garis Halus --- color: AppColors.cardBg, borderRadius: BorderRadius.circular(20), border: Border.all(color: AppColors.textSecondary.withValues(alpha: 0.15)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ // --- BERUBAH: Warna Ikon Kontroler --- Icon(Icons.settings_input_component, color: isOn ? AppColors.primary : AppColors.textSecondary, size: 16), const SizedBox(width: 6), Expanded( // --- BERUBAH: Warna Judul Kartu Kontrol --- child: Text(title, style: const TextStyle( color: AppColors.textMain, fontSize: 11, fontWeight: FontWeight.bold)), ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // --- BERUBAH: Warna Status Mode --- Text('Mode: ${mqtt.mode.toUpperCase()}', style: const TextStyle( color: AppColors.primary, fontSize: 9, fontWeight: FontWeight.bold)), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( isOn ? 'ON' : 'OFF', style: TextStyle( // --- BERUBAH: Warna Status ON/OFF --- color: isOn ? AppColors.primary : AppColors.textSecondary, fontSize: 12, fontWeight: FontWeight.bold), ), SizedBox( height: 28, child: Switch( value: isOn, onChanged: mqtt.mode == 'manual' ? onChanged : null, // --- BERUBAH: Warna Switch --- activeColor: AppColors.primary, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), ), ], ), ], ), ], ), ); } }