TKK_E32210350/lib/main.dart

515 lines
15 KiB
Dart

import 'package:cupang_app/models/FirebaseData.dart';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:intl/intl.dart'; // Import package intl
import 'package:toast/toast.dart';
import 'mqtt_service.dart'; // Import MQTT service
import 'package:flutter/services.dart'; // Import for ToastContext
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert'; // For json encoding/decoding
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: const ColorScheme.light(background: Colors.white),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
debugShowCheckedModeBanner: false,
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String? pagi;
String? siang;
String? malam;
int pagiPutaran = 0;
int siangPutaran = 0;
int malamPutaran = 0;
TimeOfDay _timeOfDay = TimeOfDay.now();
late MQTTService mqttService;
List<Map<String, dynamic>> feedingHistory = [];
@override
void initState() {
super.initState();
_loadFeedingHistory();
mqttService = MQTTService();
mqttService.initializeMQTT();
reload(); // Load data when widget is first initialized
}
@override
Widget build(BuildContext context) {
ToastContext().init(context);
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue.shade900,
title: const Text(
'PiCo',
style: TextStyle(color: Colors.white),
),
centerTitle: true,
actions: [
IconButton(
icon: Icon(Icons.history),
onPressed: _showFeedingHistory,
),
],
),
body: Column(
children: [
const SizedBox(height: 10),
buildScheduleCard('Pagi', pagi, pagiPutaran, _setPagi),
buildScheduleCard('Siang', siang, siangPutaran, _setSiang),
buildScheduleCard('Malam', malam, malamPutaran, _setMalam),
],
),
floatingActionButton: Padding(
padding: const EdgeInsets.only(bottom: 30.0),
child: FloatingActionButton.extended(
onPressed: _manualFeed,
label: const Text('Pakan Manual'),
backgroundColor: Colors.blue, // Change background color as needed
),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
);
}
Widget buildScheduleCard(
String title, String? time, int putaran, Function setFunction) {
return Container(
height: 100,
margin: const EdgeInsets.all(10),
decoration: const BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.all(Radius.circular(10)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.only(left: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (time != null)
Text(
time,
style: const TextStyle(
color: Colors.white,
fontSize: 27,
),
)
else
const CircularProgressIndicator(color: Colors.white),
Text(
title,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
),
),
Text(
'Putaran: $putaran',
style: const TextStyle(
color: Colors.white,
fontSize: 14,
),
),
],
)),
Padding(
padding: const EdgeInsets.only(right: 20),
child: ElevatedButton(
onPressed: () => setFunction(),
child: const Text('Set', style: TextStyle(color: Colors.black)),
style: ElevatedButton.styleFrom(backgroundColor: Colors.white),
),
),
],
),
);
}
Future<void> _loadFeedingHistory() async {
final prefs = await SharedPreferences.getInstance();
final String? historyString = prefs.getString('feedingHistory');
if (historyString != null) {
setState(() {
feedingHistory =
List<Map<String, dynamic>>.from(json.decode(historyString));
});
}
}
Future<void> _saveFeedingHistory(Map<String, dynamic> record) async {
final prefs = await SharedPreferences.getInstance();
// Format tanggal dan waktu
final formattedDate = DateFormat('yyyy-MM-dd').format(DateTime.now());
record['tanggal'] =
formattedDate; // Tambahkan tanggal dengan format yang diinginkan
// Tambahkan riwayat baru di awal list
feedingHistory.insert(0, record);
if (feedingHistory.length > 10) {
feedingHistory.removeLast(); // Hapus item paling lama jika melebihi 10
}
await prefs.setString('feedingHistory', json.encode(feedingHistory));
}
Future<void> _showFeedingHistory() async {
await _loadFeedingHistory();
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Riwayat Pemberian Pakan'),
content: SizedBox(
width: double.maxFinite,
child: ListView.builder(
shrinkWrap: true,
itemCount: feedingHistory.length,
itemBuilder: (BuildContext context, int index) {
final record = feedingHistory[index];
return ListTile(
title: Text(record['time']),
subtitle: Text(
'Putaran: ${record['putaran']}, Tanggal: ${record['tanggal']}'),
);
},
),
),
actions: <Widget>[
TextButton(
child: Text('OK'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
Future<void> _setPagi() async {
TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: _timeOfDay,
);
if (picked != null) {
// Validasi waktu yang dipilih
if (!isTimeInPagiRange(picked)) {
// Gunakan Builder widget untuk mendapatkan BuildContext
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Pilih waktu antara 03:00 hingga 10.59 untuk pagi'),
actions: <Widget>[
TextButton(
child: Text('OK'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
return;
}
String jam =
'${picked.hour.toString().padLeft(2, '0')}:${picked.minute.toString().padLeft(2, '0')}';
int putaran = await _showPutaranDialog();
showLoadingDialog(context);
// Update data di Firebase
await FirebaseData(pagi: jam, pagiPutaran: putaran).toJson();
Navigator.of(context).pop();
// Tampilkan toast ketika waktu pakan otomatis telah tiba
scheduleNotification(picked,
const Duration(seconds: 5)); // Notifikasi 5 detik lebih lambat
reload();
}
}
bool isTimeInPagiRange(TimeOfDay time) {
// Rentang waktu pagi adalah dari jam 03:00 hingga 10:59
return time.hour >= 3 && time.hour < 11;
}
Future<void> _setSiang() async {
TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: _timeOfDay,
);
if (picked != null) {
// Validasi waktu yang dipilih
if (!isTimeInSiangRange(picked)) {
// Gunakan Builder widget untuk mendapatkan BuildContext
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Pilih waktu antara 11:00 hingga 15:59 untuk siang'),
actions: <Widget>[
TextButton(
child: Text('OK'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
return;
}
String jam =
'${picked.hour.toString().padLeft(2, '0')}:${picked.minute.toString().padLeft(2, '0')}';
int putaran = await _showPutaranDialog();
showLoadingDialog(context);
// Update data di Firebase
await FirebaseData(siang: jam, siangPutaran: putaran).toJson();
Navigator.of(context).pop();
// Tampilkan toast ketika waktu pakan otomatis telah tiba
scheduleNotification(picked,
const Duration(seconds: 5)); // Notifikasi 5 detik lebih lambat
reload();
}
}
bool isTimeInSiangRange(TimeOfDay time) {
// Rentang waktu siang adalah dari jam 11:00 hingga 15:59
return time.hour >= 11 && time.hour < 16;
}
Future<void> _setMalam() async {
TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: _timeOfDay,
);
if (picked != null) {
// Validasi waktu yang dipilih
if (!isTimeInMalamRange(picked)) {
// Gunakan Builder widget untuk mendapatkan BuildContext
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Pilih waktu antara 16:00 hingga 02:59 untuk malam'),
actions: <Widget>[
TextButton(
child: Text('OK'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
return;
}
String jam =
'${picked.hour.toString().padLeft(2, '0')}:${picked.minute.toString().padLeft(2, '0')}';
int putaran = await _showPutaranDialog();
showLoadingDialog(context);
// Update data di Firebase
await FirebaseData(malam: jam, malamPutaran: putaran).toJson();
Navigator.of(context).pop();
// Tampilkan toast ketika waktu pakan otomatis telah tiba
scheduleNotification(picked,
const Duration(seconds: 5)); // Notifikasi 5 detik lebih lambat
reload();
}
}
bool isTimeInMalamRange(TimeOfDay time) {
// Rentang waktu malam dari jam 16:00 hingga 23:59 dan dari 00:00 hingga 02:59
return (time.hour >= 16 && time.hour <= 23) ||
(time.hour >= 0 && time.hour < 3);
}
int getPutaranForTime(TimeOfDay time) {
if (time.hour >= 3 && time.hour < 11) {
return pagiPutaran;
} else if (time.hour >= 11 && time.hour < 16) {
return siangPutaran;
} else {
return malamPutaran;
}
}
void scheduleNotification(TimeOfDay pickedTime, Duration delay) {
final now = DateTime.now();
final scheduledTime = DateTime(
now.year, now.month, now.day, pickedTime.hour, pickedTime.minute);
// Jika waktu terjadwal sudah lewat hari ini, pindahkan ke hari berikutnya
if (scheduledTime.isBefore(now)) {
scheduledTime.add(Duration(days: 1));
}
// Hitung durasi sampai notifikasi harus ditampilkan
final timeUntilNotification = scheduledTime.difference(now);
// Jika waktu notifikasi sudah lewat, jangan jadwalkan
if (timeUntilNotification.isNegative) {
// Misalnya, tampilkan pesan kesalahan atau tangani kasus ini sesuai kebutuhan
print("Waktu jadwal pakan sudah lewat. Notifikasi tidak dijadwalkan.");
return;
}
// Tambahkan delay untuk notifikasi
Timer(timeUntilNotification + delay, () async {
// Simpan riwayat pakan otomatis
await _saveFeedingHistory({
'time': pickedTime.format(context),
'putaran': getPutaranForTime(pickedTime),
// 'type': 'automatic' // Hapus field type
});
// Tampilkan notifikasi hanya setelah pakan benar-benar diberikan
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Pakan otomatis pada pukul ${pickedTime.format(context)} telah dilakukan'),
),
);
});
}
Future<void> reload() async {
final jam = await FirebaseData().getData();
setState(() {
pagi = jam.pagi;
siang = jam.siang;
malam = jam.malam;
pagiPutaran = jam.pagiPutaran ?? 0;
siangPutaran = jam.siangPutaran ?? 0;
malamPutaran = jam.malamPutaran ?? 0;
});
print([pagi, siang, malam, pagiPutaran, siangPutaran, malamPutaran]);
}
Future<int> _showPutaranDialog() async {
int putaran = 0;
await showDialog<int>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Set Putaran Servo'),
content: TextField(
keyboardType: TextInputType.number,
onChanged: (value) {
putaran = int.tryParse(value) ?? 0;
},
decoration: InputDecoration(hintText: "Masukkan jumlah putaran"),
),
actions: <Widget>[
TextButton(
child: Text('OK'),
onPressed: () {
Navigator.of(context).pop(putaran);
},
),
],
);
},
);
return putaran;
}
Future<void> _manualFeed() async {
final now = DateTime.now();
final formattedTime =
'${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}';
showLoadingDialog(context);
try {
// Menghubungkan ke broker MQTT
await mqttService.connect();
// Mengirim pesan manual feed
await mqttService.sendManualFeedMessage();
// Tambahkan penundaan sementara (misalnya 5 detik) sebelum menampilkan notifikasi
await Future.delayed(const Duration(seconds: 5));
// Tutup dialog pemrosesan setelah operasi selesai
Navigator.of(context).pop();
// Simpan riwayat pemberian pakan manual
await _saveFeedingHistory({
'time': formattedTime,
'putaran': 1, // atau nilai yang sesuai
'type': 'manual'
});
// Tampilkan notifikasi "Pemberian pakan selesai" setelah operasi selesai
Toast.show("Memberi pakan selesai :)");
} catch (e) {
// Tutup dialog pemrosesan jika ada kesalahan
Navigator.of(context).pop();
// Tampilkan notifikasi kesalahan
Toast.show("Terjadi kesalahan: $e");
}
}
void showLoadingDialog(BuildContext context) {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return const AlertDialog(
content: Row(
children: [
CircularProgressIndicator(),
SizedBox(width: 20),
Text("Memproses..."),
],
),
);
},
);
}
}