TKK_E32220150_MOBILE/realtime_data_screen.dart

302 lines
11 KiB
Dart

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<RealtimeDataScreen> createState() => _RealtimeDataScreenState();
}
class _RealtimeDataScreenState extends State<RealtimeDataScreen> {
final DatabaseReference _database = FirebaseDatabase.instance.ref();
Map<String, dynamic> 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<String, dynamic>.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<void> _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<void> _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,
),
),
],
),
),
],
),
),
);
}
}