TKK_E32222503/lib/pages/beranda.dart

514 lines
21 KiB
Dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:get/get.dart';
import 'package:incubator_app/controller/incubatorController.dart';
class Beranda extends StatefulWidget {
Beranda({super.key});
@override
State<Beranda> createState() => _BerandaState();
}
class _BerandaState extends State<Beranda> {
final IncubatorController incubatorController =
Get.put(IncubatorController());
List<FlSpot> masukSpots = [];
List<FlSpot> keluarSpots = [];
List<String> tanggalLabels = [];
ValueNotifier<bool> motorStatusSwitch = ValueNotifier(false);
void prosesData(List<Map<String, dynamic>> data) {
masukSpots.clear();
keluarSpots.clear();
tanggalLabels.clear();
final recentData = data.length <= 7 ? data : data.sublist(data.length - 7);
for (int i = 0; i < recentData.length; i++) {
final row = recentData[i];
final tgl = row['tanggal'];
final masuk = row['masuk'];
final keluar = row['keluar'];
masukSpots.add(FlSpot(i.toDouble(), masuk.toDouble()));
keluarSpots.add(FlSpot(i.toDouble(), keluar.toDouble()));
tanggalLabels.add(tgl);
}
}
double _getMaxY() {
double maxMasuk = masukSpots.isEmpty
? 0
: masukSpots.map((e) => e.y).reduce((a, b) => a > b ? a : b);
double maxKeluar = keluarSpots.isEmpty
? 0
: keluarSpots.map((e) => e.y).reduce((a, b) => a > b ? a : b);
double max = maxMasuk > maxKeluar ? maxMasuk : maxKeluar;
double adjusted = (max * 1.2).ceilToDouble();
return (adjusted / 20).ceil() * 20;
}
double _getIntervalY(double maxY) {
if (maxY <= 20) return 5;
if (maxY <= 50) return 10;
if (maxY <= 100) return 20;
if (maxY <= 200) return 40;
return 50;
}
@override
void initState() {
super.initState();
incubatorController.loadChartData().then((_) {
setState(() {
prosesData(incubatorController.chartData);
});
});
incubatorController.loadAllIncubators().then((_) {
incubatorController.cekTelurKeluarHariIni();
});
incubatorController.fetchMotorStatus();
incubatorController.fetchPompaStatus();
}
@override
Widget build(BuildContext context) {
double maxY = _getMaxY();
double intervalY = _getIntervalY(maxY);
return Scaffold(
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Center(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 65),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Selamat Datang",
style:
TextStyle(fontWeight: FontWeight.bold, fontSize: 24),
),
SizedBox(width: 8),
Obx(() {
int notifCount =
incubatorController.telurKeluarHariIni.length;
return Stack(
children: [
IconButton(
icon: const Icon(Icons.notifications,
size: 32, color: Color(0xFF00A1FF)),
onPressed: () {
final telurHariIni =
incubatorController.telurKeluarHariIni;
if (telurHariIni.isEmpty) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("Notifikasi"),
content: const Text(
"Tidak ada telur keluar hari ini."),
actions: [
TextButton(
onPressed: () =>
Navigator.of(context).pop(),
child: const Text("Tutup"),
),
],
),
);
} else {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text("Telur Keluar Hari Ini"),
content: SizedBox(
width: double.maxFinite,
child: ListView.builder(
shrinkWrap: true,
itemCount: telurHariIni.length,
itemBuilder: (context, index) {
final item = telurHariIni[index];
return ListTile(
leading: const Icon(Icons.egg,
color: Colors.orangeAccent),
title:
Text("Kode: ${item['kode']}"),
subtitle: Text(
"Jumlah Telur: ${item['jumlah_telur']} telur\nJumlah Menetas: ${item['jumlah_menetas']} telur\nJumlah Belum Menetas: ${item['jumlah_belum_menetas']}"),
);
},
),
),
actions: [
TextButton(
onPressed: () =>
Navigator.of(context).pop(),
child: const Text("Tutup"),
),
],
),
);
}
},
),
if (notifCount > 0)
Positioned(
right: 4,
top: 4,
child: Container(
padding: const EdgeInsets.all(5),
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: Text(
'$notifCount',
style: const TextStyle(
color: Colors.white,
fontSize: 10,
),
),
),
),
],
);
}),
],
),
const SizedBox(height: 40),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Obx(
() => InfoCard(
title: "Suhu",
icon: Icons.thermostat_outlined,
value:
"${incubatorController.suhu.value.toStringAsFixed(1)}"),
),
const SizedBox(width: 15),
Obx(
() => InfoCard(
title: "Kelembapan",
icon: Icons.water_drop_outlined,
value:
"${incubatorController.kelembapan.value.toStringAsFixed(1)}%"),
),
],
),
const SizedBox(height: 20),
Obx(() => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"Motor Status",
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.w500),
),
CupertinoSwitch(
value: incubatorController.motorStatus.value,
onChanged: (newValue) {
incubatorController.updateMotorStatus(newValue);
},
activeColor: Color(0xFF00A1FF),
),
],
)),
const SizedBox(height: 10),
Obx(() => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"Pompa Status",
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.w500),
),
CupertinoSwitch(
value: incubatorController.pumpStatus.value,
onChanged: (newValue) {
incubatorController.updatePompaStatus(newValue);
},
activeColor: Color(0xFF00A1FF),
),
],
)),
const SizedBox(height: 20),
const Text(
"History Telur Incubator",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w400),
),
const SizedBox(height: 20),
if (masukSpots.isEmpty && keluarSpots.isEmpty)
Center(
heightFactor: 5,
child: Text("Data Telur Tidak ada",
style:
TextStyle(fontSize: 18, color: Color(0xFF737373))),
)
else
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: AspectRatio(
aspectRatio: 1.7,
child: LineChart(
LineChartData(
gridData:
FlGridData(show: true, drawVerticalLine: false),
titlesData: FlTitlesData(
show: true,
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false)),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false)),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 40,
interval: intervalY,
getTitlesWidget: (value, _) =>
Text(value.toInt().toString()),
),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 32,
interval: 1,
getTitlesWidget: (value, _) {
int index = value.toInt();
if (index >= 0 &&
index < tanggalLabels.length) {
String tanggal = tanggalLabels[index];
return Text(
tanggal.length >= 10
? tanggal.substring(5, 10)
: tanggal,
style: const TextStyle(fontSize: 10),
);
}
return const SizedBox.shrink();
},
),
),
),
borderData: FlBorderData(
show: true,
border: Border.all(color: Colors.black, width: 1),
),
minX: 0,
maxX: (tanggalLabels.length - 1).toDouble(),
minY: 0,
maxY: maxY,
lineBarsData: [
LineChartBarData(
spots: masukSpots,
isCurved: true,
color: Colors.green,
barWidth: 3,
isStrokeCapRound: true,
dotData: FlDotData(show: true),
belowBarData: BarAreaData(show: false),
),
LineChartBarData(
spots: keluarSpots,
isCurved: true,
color: Colors.red,
barWidth: 3,
isStrokeCapRound: true,
dotData: FlDotData(show: true),
belowBarData: BarAreaData(show: false),
),
],
),
),
),
),
),
// ==== Grafik Suhu & Kelembapan ====
const SizedBox(height: 20),
const Text(
"Grafik Suhu & Kelembapan",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w400),
),
const SizedBox(height: 20),
Obx(() {
if (incubatorController.suhuSpots.isEmpty ||
incubatorController.kelembapanSpots.isEmpty) {
return Center(
heightFactor: 3,
child: Text("Belum ada data suhu & kelembapan.",
style: TextStyle(
fontSize: 16, color: Color(0xFF737373))),
);
}
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
child: AspectRatio(
aspectRatio: 1.7,
child: LineChart(
LineChartData(
gridData:
FlGridData(show: true, drawVerticalLine: false),
titlesData: FlTitlesData(
show: true,
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false)),
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false)),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 28,
interval: 1,
getTitlesWidget: (value, _) {
int index = value.toInt();
if (index >= 0 &&
index <
incubatorController
.waktuLabels.length) {
return Text(
incubatorController.waktuLabels[index],
style: const TextStyle(fontSize: 10),
);
}
return const SizedBox.shrink();
},
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 42,
interval: 20,
getTitlesWidget: (value, _) =>
Text(value.toInt().toString()),
),
),
),
borderData: FlBorderData(
show: true,
border: Border.all(color: Colors.black, width: 1),
),
minX: 0,
maxX: (incubatorController.waktuLabels.length - 1)
.toDouble(),
minY: 0,
maxY: 100,
lineBarsData: [
LineChartBarData(
spots: incubatorController.suhuSpots.length > 10
? incubatorController.suhuSpots.sublist(
incubatorController.suhuSpots.length -
10)
: incubatorController.suhuSpots,
isCurved: true,
color: Colors.orange,
barWidth: 4,
isStrokeCapRound: true,
dotData: FlDotData(show: false),
belowBarData: BarAreaData(show: false),
),
LineChartBarData(
spots:
incubatorController.kelembapanSpots.length >
10
? incubatorController.kelembapanSpots
.sublist(incubatorController
.kelembapanSpots.length -
10)
: incubatorController.kelembapanSpots,
isCurved: true,
color: Colors.blue,
barWidth: 4,
isStrokeCapRound: true,
dotData: FlDotData(show: false),
belowBarData: BarAreaData(show: false),
),
],
),
),
),
),
);
}),
],
),
),
),
),
);
}
}
class InfoCard extends StatelessWidget {
final String title;
final IconData icon;
final String value;
InfoCard({
super.key,
required this.title,
required this.icon,
required this.value,
});
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final screenHeight = MediaQuery.of(context).size.height;
final cardWidth = screenWidth * 0.44;
final cardHeight = screenHeight * 0.22;
return Container(
height: cardHeight,
width: cardWidth,
decoration: BoxDecoration(
color: const Color(0xFF00A1FF),
borderRadius: BorderRadius.circular(15),
),
padding: const EdgeInsets.all(15),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
title,
style: TextStyle(
fontSize: 21, fontWeight: FontWeight.w600, color: Colors.white),
),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, size: 48, color: Colors.white),
Text(
value,
style: TextStyle(
fontSize: 28,
fontWeight: FontWeight.w500,
color: Colors.white),
),
],
),
],
),
);
}
}