import 'dart:async'; import 'package:flutter/material.dart'; import 'package:firebase_database/firebase_database.dart'; class RealtimeDataScreen extends StatefulWidget { const RealtimeDataScreen({Key? key}) : super(key: key); @override State createState() => _RealtimeDataScreenState(); } class _RealtimeDataScreenState extends State { final DatabaseReference _database = FirebaseDatabase.instance.ref(); Map sensorData = { 'humidity': 0, 'soilMoisture': 0, 'temperature': 0, }; bool isLoading = true; late Timer _imageRefreshTimer; String _imageUrl = ''; String? _scheduledTime; @override void initState() { super.initState(); _setupRealtimeListener(); _startImageAutoRefresh(); _fetchSchedule(); } void _setupRealtimeListener() { _database.child('sensors').onValue.listen((event) { if (event.snapshot.value != null) { setState(() { sensorData = Map.from(event.snapshot.value as Map); isLoading = false; }); } }); } void _startImageAutoRefresh() { _refreshImageUrl(); _imageRefreshTimer = Timer.periodic(const Duration(seconds: 10), (timer) { _refreshImageUrl(); }); } void _refreshImageUrl() { setState(() { _imageUrl = 'https://nwlikynouhddjwyjrwnl.supabase.co/storage/v1/object/public/photo/latest.jpg?t=${DateTime.now().millisecondsSinceEpoch}'; }); } Future _fetchSchedule() async { final snapshot = await _database.child('camera/schedule').get(); if (snapshot.exists) { setState(() { _scheduledTime = snapshot.value.toString().trim().isEmpty ? null : snapshot.value.toString(); }); } } Future _pickSchedule() async { final TimeOfDay? picked = await showTimePicker( context: context, initialTime: TimeOfDay.now(), builder: (context, child) { return MediaQuery( data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true), child: child!, ); }, ); if (picked != null) { final hour = picked.hour.toString().padLeft(2, '0'); final minute = picked.minute.toString().padLeft(2, '0'); final formatted = '$hour.$minute'; try { await _database.child('camera/schedule').set(formatted); setState(() { _scheduledTime = formatted; }); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Gambar dijadwalkan pada $formatted'), backgroundColor: Colors.green, ), ); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Gagal menjadwalkan: $e'), backgroundColor: Colors.red, ), ); } } } @override void dispose() { _imageRefreshTimer.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Data Sensor & Kamera'), backgroundColor: Colors.green, ), body: isLoading ? const Center(child: CircularProgressIndicator()) : RefreshIndicator( onRefresh: () async { setState(() { isLoading = true; }); _setupRealtimeListener(); await _fetchSchedule(); }, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildSensorCard( 'Kelembaban Udara', '${sensorData['humidity']?.toStringAsFixed(1) ?? 'N/A'}%', Icons.water, Colors.blue, ), const SizedBox(height: 16), _buildSensorCard( 'Kelembaban Tanah', '${sensorData['soilMoisture']?.toStringAsFixed(1) ?? 'N/A'}%', Icons.water_drop, Colors.lightBlue, ), const SizedBox(height: 16), _buildSensorCard( 'Suhu', '${sensorData['temperature']?.toStringAsFixed(1) ?? 'N/A'}°C', Icons.thermostat, Colors.orange, ), const SizedBox(height: 24), // Combined Container: Jadwal + Lihat Gambar Container( padding: const EdgeInsets.all(16), margin: const EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration( color: Colors.orange.shade50, borderRadius: BorderRadius.circular(16), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Jadwal Pengambilan Gambar:', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), Text( _scheduledTime ?? 'Belum ada jadwal', style: const TextStyle( fontSize: 24, color: Colors.black87), ), const SizedBox(height: 16), Row( children: [ Expanded( child: ElevatedButton.icon( icon: const Icon(Icons.schedule), label: const Text('Atur Jadwal Pengambilan'), style: ElevatedButton.styleFrom( backgroundColor: Colors.orange, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( vertical: 14, horizontal: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), ), onPressed: _pickSchedule, ), ), const SizedBox(width: 12), Expanded( child: ElevatedButton.icon( icon: const Icon(Icons.image), label: const Text('Lihat Gambar Terbaru'), style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( vertical: 14, horizontal: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), ), onPressed: () { _refreshImageUrl(); showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Gambar Terbaru'), content: Image.network( _imageUrl, fit: BoxFit.contain, key: ValueKey(_imageUrl), errorBuilder: (context, error, stackTrace) => const Text( 'Gagal memuat gambar.'), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Tutup'), ), ], ), ); }, ), ), ], ), ], ), ), ], ), ), ), ); } Widget _buildSensorCard( String title, String value, IconData icon, Color color) { return Card( elevation: 4, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), child: Padding( padding: const EdgeInsets.all(16.0), child: Row( children: [ Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Icon(icon, color: color, size: 32), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 4), Text( value, style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: color, ), ), ], ), ), ], ), ), ); } }