493 lines
22 KiB
Dart
493 lines
22 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'models/sensor_data.dart';
|
|
import 'services/firebase_service.dart';
|
|
import 'services/notification_service.dart';
|
|
import 'dart:async';
|
|
|
|
class HomePage extends StatefulWidget {
|
|
const HomePage({super.key});
|
|
|
|
@override
|
|
State<HomePage> createState() => _HomePageState();
|
|
}
|
|
|
|
class _HomePageState extends State<HomePage> {
|
|
final FirebaseService _firebaseService = FirebaseService();
|
|
final TextEditingController _jamController = TextEditingController();
|
|
final TextEditingController _menitController = TextEditingController();
|
|
final TextEditingController _detikController = TextEditingController();
|
|
final TextEditingController _suhuMaksController = TextEditingController();
|
|
final TextEditingController _suhuMinController = TextEditingController();
|
|
final TextEditingController _kadarAirController = TextEditingController();
|
|
|
|
int? _lastTimer;
|
|
Timer? _koneksiTimer;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
NotificationService.initialize();
|
|
_firebaseService.syncPathToFirestore('sensorData', 'sensorData_firestore');
|
|
_firebaseService.listenAndMirror('sensorData', 'sensorData_firestore');
|
|
_koneksiTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
_firebaseService.updateKoneksiAndroid(true);
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_koneksiTimer?.cancel();
|
|
_jamController.dispose();
|
|
_menitController.dispose();
|
|
_detikController.dispose();
|
|
_suhuMaksController.dispose();
|
|
_suhuMinController.dispose();
|
|
_kadarAirController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
String formatWaktu(int totalDetik) {
|
|
final jam = totalDetik ~/ 3600;
|
|
final sisaDetik = totalDetik % 3600;
|
|
final menit = sisaDetik ~/ 60;
|
|
final detik = sisaDetik % 60;
|
|
return '${jam.toString().padLeft(2, '0')}:'
|
|
'${menit.toString().padLeft(2, '0')}:'
|
|
'${detik.toString().padLeft(2, '0')}';
|
|
}
|
|
|
|
void _checkTimer(int currentTimer) {
|
|
if (_lastTimer != null && _lastTimer! > 0 && currentTimer == 0) {
|
|
NotificationService.showNotification(
|
|
title: 'Timer Selesai',
|
|
body: 'Proses pengeringan jagung telah selesai!',
|
|
);
|
|
}
|
|
_lastTimer = currentTimer;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: Colors.blueGrey.shade50,
|
|
appBar: AppBar(
|
|
title: const Center(child: Text('Smartcorn Dryer')),
|
|
backgroundColor: Colors.blue.shade800,
|
|
foregroundColor: Colors.white,
|
|
),
|
|
body: StreamBuilder<SensorData>(
|
|
stream: _firebaseService.getSensorData(),
|
|
builder: (context, snapshot) {
|
|
if (snapshot.hasError) {
|
|
return Center(child: Text('Error: ${snapshot.error}'));
|
|
}
|
|
if (!snapshot.hasData) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
|
|
final data = snapshot.data!;
|
|
_checkTimer(data.timer);
|
|
|
|
return SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Data Sensor Card
|
|
Card(
|
|
elevation: 4,
|
|
color: Colors.white,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Data Sensor',
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.titleLarge
|
|
?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.blueGrey.shade800),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Icon(Icons.thermostat,
|
|
color: Colors.blue.shade700),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'Suhu: ${data.suhu.toStringAsFixed(2)}°C',
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.titleMedium
|
|
?.copyWith(color: Colors.blueGrey.shade800),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Pengaturan Timer & Suhu Card
|
|
Card(
|
|
elevation: 4,
|
|
color: Colors.white,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Pengaturan Timer (Otomatisasi)',
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.titleLarge
|
|
?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.blueGrey.shade800),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: TextField(
|
|
controller: _jamController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
labelText: 'Jam',
|
|
border: OutlineInputBorder(),
|
|
labelStyle: TextStyle(color: Colors.blueGrey),
|
|
enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.blueGrey)),
|
|
focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.blue)),
|
|
),
|
|
style: TextStyle(color: Colors.blueGrey.shade900),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: TextField(
|
|
controller: _menitController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
labelText: 'Menit',
|
|
border: OutlineInputBorder(),
|
|
labelStyle: TextStyle(color: Colors.blueGrey),
|
|
enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.blueGrey)),
|
|
focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.blue)),
|
|
),
|
|
style: TextStyle(color: Colors.blueGrey.shade900),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: TextField(
|
|
controller: _detikController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
labelText: 'Detik',
|
|
border: OutlineInputBorder(),
|
|
labelStyle: TextStyle(color: Colors.blueGrey),
|
|
enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.blueGrey)),
|
|
focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.blue)),
|
|
),
|
|
style: TextStyle(color: Colors.blueGrey.shade900),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Column(
|
|
children: [
|
|
SizedBox(
|
|
width: 110,
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
final jam = int.tryParse(_jamController.text) ?? 0;
|
|
final menit = int.tryParse(_menitController.text) ?? 0;
|
|
final detik = int.tryParse(_detikController.text) ?? 0;
|
|
final totalDetik = jam * 3600 + menit * 60 + detik;
|
|
_firebaseService.updateTimer(totalDetik);
|
|
_jamController.clear();
|
|
_menitController.clear();
|
|
_detikController.clear();
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blue.shade700,
|
|
foregroundColor: Colors.white,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(30),
|
|
),
|
|
),
|
|
child: const Text('Atur\nTimer', textAlign: TextAlign.center),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
SizedBox(
|
|
width: 110,
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
_firebaseService.resetTimer();
|
|
_firebaseService.updateManualMode(true);
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color.fromARGB(255, 255, 0, 0),
|
|
foregroundColor: Colors.white,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(30),
|
|
),
|
|
),
|
|
child: const Text('Reset\nTimer', textAlign: TextAlign.center),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'Batas Suhu',
|
|
style: TextStyle(
|
|
fontSize: 16, fontWeight: FontWeight.bold,
|
|
color: Colors.blueGrey.shade800),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: TextField(
|
|
controller: _suhuMinController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
labelText: 'Minimal (°C)',
|
|
border: OutlineInputBorder(),
|
|
labelStyle: TextStyle(color: Colors.blueGrey),
|
|
enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.blueGrey)),
|
|
focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.blue)),
|
|
),
|
|
style: TextStyle(color: Colors.blueGrey.shade900),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: TextField(
|
|
controller: _suhuMaksController,
|
|
keyboardType: TextInputType.number,
|
|
decoration: InputDecoration(
|
|
labelText: 'Maksimal (°C)',
|
|
border: OutlineInputBorder(),
|
|
labelStyle: TextStyle(color: Colors.blueGrey),
|
|
enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.blueGrey)),
|
|
focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.blue)),
|
|
),
|
|
style: TextStyle(color: Colors.blueGrey.shade900),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
SizedBox(
|
|
width: 120,
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
_firebaseService.updateSuhuMin(
|
|
int.tryParse(_suhuMinController.text) ??
|
|
0);
|
|
_firebaseService.updateSuhuMaks(
|
|
int.tryParse(_suhuMaksController.text) ??
|
|
0);
|
|
_suhuMinController.clear();
|
|
_suhuMaksController.clear();
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blue.shade700,
|
|
foregroundColor: Colors.white,
|
|
),
|
|
child: const Text('Atur Suhu'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Icon(Icons.timer,
|
|
color: Colors.blue.shade700),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'Sisa Waktu: ${formatWaktu(data.timer)}',
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.titleMedium
|
|
?.copyWith(color: Colors.blueGrey.shade800),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Icon(Icons.thermostat_outlined,
|
|
color: Colors.blue.shade700),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'Batas Suhu saat ini: '
|
|
'${data.suhuMin}°C - ${data.suhuMaks}°C',
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.titleMedium
|
|
?.copyWith(color: Colors.blueGrey.shade800),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Icon(Icons.power_settings_new,
|
|
color: Colors.blue.shade700),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'Status Relay: ${data.elemen.toUpperCase()}',
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.titleMedium
|
|
?.copyWith(color: Colors.blueGrey.shade800),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 24),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Kontrol Manual Card
|
|
Card(
|
|
elevation: 4,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Kontrol Manual',
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.titleLarge
|
|
?.copyWith(fontWeight: FontWeight.bold),
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Mode Manual
|
|
ListTile(
|
|
contentPadding: EdgeInsets.zero,
|
|
title: const Text('Mode Manual'),
|
|
subtitle:
|
|
Text(data.manualMode ? 'Aktif' : 'Tidak Aktif'),
|
|
trailing: SizedBox(
|
|
width: 60,
|
|
child: Switch(
|
|
value: data.manualMode,
|
|
onChanged: (value) {
|
|
_firebaseService.updateManualMode(value);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
|
|
// Kontrol Elemen
|
|
ListTile(
|
|
contentPadding: EdgeInsets.zero,
|
|
title: const Text('Kontrol Elemen'),
|
|
subtitle:
|
|
Text(data.manualButton ? 'Menyala' : 'Mati'),
|
|
trailing: SizedBox(
|
|
width: 60,
|
|
child: Switch(
|
|
value: data.manualButton,
|
|
onChanged: (value) {
|
|
_firebaseService.updateManualButton(value);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Card(
|
|
elevation: 4,
|
|
color: Colors.white,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Input Kadar Air',
|
|
style: Theme.of(context)
|
|
.textTheme
|
|
.titleLarge
|
|
?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.blueGrey.shade800),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: TextField(
|
|
controller: _kadarAirController,
|
|
keyboardType: TextInputType.numberWithOptions(
|
|
decimal: true),
|
|
decoration: InputDecoration(
|
|
labelText: 'Kadar Air (%)',
|
|
border: OutlineInputBorder(),
|
|
labelStyle: TextStyle(color: Colors.blueGrey),
|
|
enabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.blueGrey)),
|
|
focusedBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.blue)),
|
|
),
|
|
style: TextStyle(color: Colors.blueGrey.shade900),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
SizedBox(
|
|
width: 120,
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
final kadarAir =
|
|
double.tryParse(_kadarAirController.text);
|
|
if (kadarAir != null) {
|
|
_firebaseService.updateKadarAir(kadarAir);
|
|
_kadarAirController.clear();
|
|
} else {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Input tidak valid')),
|
|
);
|
|
}
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blue.shade700,
|
|
foregroundColor: Colors.white,
|
|
),
|
|
child: const Text('Kirim'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
}
|