TKK_E32220213/code aplikasi/lib/home.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'),
),
),
],
),
],
),
),
),
],
),
);
},
),
);
}
}