TKK_E32231225/lib/screens/dashboard_screen.dart

382 lines
10 KiB
Dart

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:audioplayers/audioplayers.dart';
import '../widgets/moisture_gauge.dart';
import '../widgets/pir_status_card.dart';
import 'manual_screen.dart';
class DashboardScreen extends StatefulWidget {
const DashboardScreen({super.key});
@override
State<DashboardScreen> createState() => _DashboardScreenState();
}
class _DashboardScreenState extends State<DashboardScreen> {
final dbRef = FirebaseDatabase.instanceFor(
app: Firebase.app(),
databaseURL: "https://tomat2-default-rtdb.asia-southeast1.firebasedatabase.app",
).ref();
int moisture = 0;
bool pirDetected = false;
String mode = "auto";
int min = 60;
int max = 80;
int pump = 0;
int index = 0;
/// ===== CHART =====
List<FlSpot> spots = [];
int xIndex = 0;
/// ===== EMA =====
double ema = 0;
double alpha = 0.2;
double getEMA(double value) {
if (ema == 0) {
ema = value;
} else {
ema = alpha * value + (1 - alpha) * ema;
}
return ema;
}
/// ===== AUDIO =====
final AudioPlayer player = AudioPlayer();
bool isPlaying = false;
/// ===== PIR CONTROL =====
bool lastPir = false;
DateTime lastTriggerTime = DateTime.now();
Future<void> triggerAlarm() async {
final now = DateTime.now();
if (now.difference(lastTriggerTime).inSeconds < 10) return;
lastTriggerTime = now;
if (isPlaying) return;
isPlaying = true;
await player.play(AssetSource('sounds/alert.mp3'));
Future.delayed(const Duration(seconds: 15), () async {
await player.stop();
isPlaying = false;
});
}
@override
void initState() {
super.initState();
/// ===== MOISTURE =====
dbRef.child("sensor/moisture").onValue.listen((event) {
final val = int.tryParse(event.snapshot.value.toString()) ?? 0;
final smooth = getEMA(val.toDouble());
setState(() {
moisture = val;
spots.add(FlSpot(xIndex.toDouble(), smooth));
xIndex++;
if (spots.length > 20) {
spots.removeAt(0);
}
});
});
/// ===== PIR =====
dbRef.child("sensor/pir").onValue.listen((event) {
final detected = event.snapshot.value.toString() == "1";
setState(() {
pirDetected = detected;
});
if (detected && !lastPir) {
triggerAlarm(); // 🔊 alarm lokal
}
lastPir = detected;
});
/// ===== MODE =====
dbRef.child("control/mode").onValue.listen((event) {
setState(() {
mode = event.snapshot.value.toString();
});
});
/// ===== CONFIG =====
dbRef.child("config/min_moisture").onValue.listen((event) {
setState(() {
min = int.tryParse(event.snapshot.value.toString()) ?? 60;
});
});
dbRef.child("config/max_moisture").onValue.listen((event) {
setState(() {
max = int.tryParse(event.snapshot.value.toString()) ?? 80;
});
});
/// ===== PUMP =====
dbRef.child("control/pump").onValue.listen((event) {
setState(() {
pump = int.tryParse(event.snapshot.value.toString()) ?? 0;
});
});
}
Future<void> setMode(String newMode) async {
await dbRef.child("control/mode").set(newMode);
}
@override
void dispose() {
player.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
title: const Text("Monitoring Tomat"),
backgroundColor: Colors.black.withOpacity(0.6),
elevation: 0,
),
body: Stack(
children: [
Positioned.fill(
child: Image.asset(
"assets/images/bg.jpg",
fit: BoxFit.cover,
),
),
Positioned.fill(
child: Container(
color: Colors.black.withOpacity(0.6),
),
),
Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Row(
children: [
Expanded(
child: _buildCard(
child: MoistureGauge(
value: moisture,
min: min,
max: max,
),
),
),
const SizedBox(width: 12),
Expanded(
child: _buildCard(
child: PirStatusCard(
pirDetected: pirDetected,
),
),
),
],
),
const SizedBox(height: 20),
_buildCard(
height: 180,
child: spots.isEmpty
? const Center(
child: Text(
"Waiting Data...",
style: TextStyle(color: Colors.white38),
),
)
: LineChart(
LineChartData(
minY: 0,
maxY: 100,
gridData: FlGridData(show: false),
titlesData: FlTitlesData(show: false),
borderData: FlBorderData(show: false),
extraLinesData: ExtraLinesData(
horizontalLines: [
HorizontalLine(
y: min.toDouble(),
strokeWidth: 1,
color: Colors.orangeAccent,
),
HorizontalLine(
y: max.toDouble(),
strokeWidth: 1,
color: Colors.blueAccent,
),
],
),
lineBarsData: [
LineChartBarData(
spots: spots,
isCurved: true,
barWidth: 3,
gradient: const LinearGradient(
colors: [
Colors.greenAccent,
Colors.green
],
),
dotData: FlDotData(show: false),
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
colors: [
Colors.greenAccent.withOpacity(0.3),
Colors.transparent,
],
),
),
),
],
),
),
),
const SizedBox(height: 20),
_buildCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
mode == "auto"
? Icons.smart_toy
: Icons.touch_app,
color: mode == "auto"
? Colors.greenAccent
: Colors.orangeAccent,
),
const SizedBox(width: 10),
Text(
"Mode: ${mode.toUpperCase()}",
style: const TextStyle(color: Colors.white),
),
],
),
const SizedBox(height: 10),
Text(
_getPumpStatus(),
style: TextStyle(color: _getPumpColor()),
),
const SizedBox(height: 10),
Text(
"Min: $min | Max: $max",
style: const TextStyle(color: Colors.white54),
),
],
),
),
],
),
),
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: index,
backgroundColor: const Color(0xFF020617),
selectedItemColor: Colors.greenAccent,
unselectedItemColor: Colors.white54,
onTap: (i) async {
if (i == index) return;
setState(() => index = i);
if (i == 0) {
await setMode("auto");
Navigator.popUntil(context, (route) => route.isFirst);
}
if (i == 1) {
await setMode("manual");
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => const ManualScreen(),
),
);
}
},
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: "Auto",
),
BottomNavigationBarItem(
icon: Icon(Icons.settings_remote),
label: "Manual",
),
],
),
);
}
Widget _buildCard({required Widget child, double? height}) {
return Container(
height: height,
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFF1E293B).withOpacity(0.85),
borderRadius: BorderRadius.circular(16),
),
child: child,
);
}
String _getPumpStatus() {
if (mode == "manual") {
return pump == 1 ? "Pump ON (Manual)" : "Pump OFF (Manual)";
}
if (moisture < min) return "Pump ON (Dry)";
if (moisture > max) return "Pump OFF (Wet)";
return pump == 1 ? "Pump ON (Holding)" : "Pump OFF (Holding)";
}
Color _getPumpColor() {
return pump == 1 ? Colors.greenAccent : Colors.redAccent;
}
}