TKK_E32230688/lib/screen/dasboart.dart

399 lines
11 KiB
Dart

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:firebase_auth/firebase_auth.dart';
class DashboardPage extends StatefulWidget {
const DashboardPage({super.key});
@override
State<DashboardPage> createState() => _DashboardPageState();
}
class _DashboardPageState extends State<DashboardPage> {
final DatabaseReference dbRef =
FirebaseDatabase.instance.ref("fermentasi");
double s1 = 0;
double s2 = 0;
double s3 = 0;
double suhuMin = 0;
double suhuMax = 0;
double suhuRata = 0;
double prediksi = 0;
String heater = "OFF";
String fanHot = "OFF";
String fanCold = "OFF";
List<FlSpot> spots1 = [];
List<FlSpot> spots2 = [];
List<FlSpot> spots3 = [];
int indexChart = 0;
bool isRefreshing = false;
@override
void initState() {
super.initState();
listenFirebase();
}
// ================= FIREBASE LISTENER =================
void listenFirebase() {
dbRef.onValue.listen((event) {
if (event.snapshot.value == null) return;
final raw = event.snapshot.value as Map;
final data = raw.map(
(key, value) => MapEntry(key.toString(), value),
);
setState(() {
// SENSOR
s1 = double.tryParse(data["sensor1"].toString()) ?? 0;
s2 = double.tryParse(data["sensor2"].toString()) ?? 0;
s3 = double.tryParse(data["sensor3"].toString()) ?? 0;
// STATISTIK
suhuMin = double.tryParse(data["min"].toString()) ?? 0;
suhuMax = double.tryParse(data["max"].toString()) ?? 0;
suhuRata = double.tryParse(data["rata_rata"].toString()) ?? 0;
prediksi = double.tryParse(data["prediksi"].toString()) ?? 0;
// STATUS
heater = data["status"]?["pemanas"]?.toString() ?? "OFF";
fanHot = data["status"]?["kipas_panas"]?.toString() ?? "OFF";
fanCold = data["status"]?["kipas_dingin"]?.toString() ?? "OFF";
// ================= REALTIME GRAPH =================
spots1.add(FlSpot(indexChart.toDouble(), s1));
spots2.add(FlSpot(indexChart.toDouble(), s2));
spots3.add(FlSpot(indexChart.toDouble(), s3));
indexChart++;
// limit data biar ringan
if (spots1.length > 40) {
spots1.removeAt(0);
spots2.removeAt(0);
spots3.removeAt(0);
}
});
});
}
// ================= LOGOUT =================
Future<void> logout() async {
await FirebaseAuth.instance.signOut();
}
// ================= REFRESH BUTTON =================
Future<void> refreshData() async {
setState(() => isRefreshing = true);
await Future.delayed(const Duration(milliseconds: 500));
setState(() => isRefreshing = false);
}
// ================= GRAPH RANGE =================
double getMinY() {
List<double> all = [
...spots1.map((e) => e.y),
...spots2.map((e) => e.y),
...spots3.map((e) => e.y),
];
if (all.isEmpty) return 29;
return (all.reduce(min) - 1);
}
double getMaxY() {
List<double> all = [
...spots1.map((e) => e.y),
...spots2.map((e) => e.y),
...spots3.map((e) => e.y),
];
if (all.isEmpty) return 35;
return (all.reduce(max) + 1);
}
// ================= UI =================
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Dashboard Fermentasi"),
actions: [
IconButton(
icon: const Icon(Icons.logout),
onPressed: logout,
),
],
),
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF5F7F7A), Color(0xFFE6E9E8)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
child: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"MONITORING FERMENTASI TEMPE",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 15),
buildChart(),
const SizedBox(height: 15),
buildSensor(),
const SizedBox(height: 15),
buildStatistik(),
const SizedBox(height: 15),
buildStatus(),
const SizedBox(height: 15),
// ================= REFRESH BUTTON =================
buildRefresh(),
],
),
),
),
),
);
}
// ================= CHART =================
Widget buildChart() {
return Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: const [
Legend("Sensor 1", Colors.blue),
Legend("Sensor 2", Colors.green),
Legend("Sensor 3", Colors.orange),
],
),
const SizedBox(height: 10),
SizedBox(
height: 230,
child: LineChart(
LineChartData(
minY: getMinY(),
maxY: getMaxY(),
gridData: const FlGridData(show: true),
lineBarsData: [
lineData(Colors.blue, spots1),
lineData(Colors.green, spots2),
lineData(Colors.orange, spots3),
],
),
),
),
],
),
);
}
// ================= SENSOR =================
Widget buildSensor() {
return Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("SENSOR",
style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
sensorRow("Sensor 1", s1, Colors.blue),
sensorRow("Sensor 2", s2, Colors.green),
sensorRow("Sensor 3", s3, Colors.orange),
],
),
);
}
// ================= STATISTIK =================
Widget buildStatistik() {
return Row(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("STATISTIK",
style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
itemStat("Minimum", suhuMin),
itemStat("Maksimum", suhuMax),
itemStat("Rata-rata", suhuRata),
],
),
),
),
const SizedBox(width: 10),
Expanded(
child: Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
),
child: Column(
children: [
const Text("PREDIKSI",
style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Text(
"${prediksi.toStringAsFixed(2)}°C",
style: const TextStyle(
fontSize: 26,
color: Colors.blue,
fontWeight: FontWeight.bold,
),
),
const Text("5 menit ke depan"),
],
),
),
),
],
);
}
// ================= STATUS =================
Widget buildStatus() {
return Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(15),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text("STATUS SISTEM",
style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
statusRow("Pemanas", heater),
statusRow("Kipas Pemanas", fanHot),
statusRow("Kipas Pendingin", fanCold),
],
),
);
}
// ================= REFRESH BUTTON =================
Widget buildRefresh() {
return SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: isRefreshing ? null : refreshData,
child: isRefreshing
? const CircularProgressIndicator(color: Colors.white)
: const Text("Refresh Data"),
),
);
}
// ================= WIDGET =================
Widget sensorRow(String label, double value, Color color) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label),
Text("${value.toStringAsFixed(2)}°C",
style: TextStyle(color: color, fontWeight: FontWeight.bold)),
],
);
}
Widget itemStat(String title, double val) {
Color warna = (val < 30 || val > 40) ? Colors.red : Colors.black;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(title, style: TextStyle(color: warna)),
Text("${val.toStringAsFixed(2)}°C",
style: TextStyle(color: warna, fontWeight: FontWeight.bold)),
],
);
}
Widget statusRow(String label, String value) {
Color warna = value == "ON" ? Colors.red : Colors.grey;
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label),
Text(value,
style: TextStyle(color: warna, fontWeight: FontWeight.bold)),
],
);
}
LineChartBarData lineData(Color color, List<FlSpot> spots) {
return LineChartBarData(
spots: spots,
isCurved: true,
color: color,
dotData: const FlDotData(show: false),
);
}
}
class Legend extends StatelessWidget {
final String text;
final Color color;
const Legend(this.text, this.color, {super.key});
@override
Widget build(BuildContext context) {
return Row(
children: [
CircleAvatar(radius: 5, backgroundColor: color),
const SizedBox(width: 5),
Text(text),
],
);
}
}