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 createState() => _MyHomePageState(); } class _MyHomePageState extends State { String? pagi; String? siang; String? malam; int pagiPutaran = 0; int siangPutaran = 0; int malamPutaran = 0; TimeOfDay _timeOfDay = TimeOfDay.now(); late MQTTService mqttService; List> 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 _loadFeedingHistory() async { final prefs = await SharedPreferences.getInstance(); final String? historyString = prefs.getString('feedingHistory'); if (historyString != null) { setState(() { feedingHistory = List>.from(json.decode(historyString)); }); } } Future _saveFeedingHistory(Map 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 _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: [ TextButton( child: Text('OK'), onPressed: () { Navigator.of(context).pop(); }, ), ], ); }, ); } Future _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: [ 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 _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: [ 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 _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: [ 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 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 _showPutaranDialog() async { int putaran = 0; await showDialog( 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: [ TextButton( child: Text('OK'), onPressed: () { Navigator.of(context).pop(putaran); }, ), ], ); }, ); return putaran; } Future _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..."), ], ), ); }, ); } }