365 lines
11 KiB
Dart
365 lines
11 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:firebase_database/firebase_database.dart';
|
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
import 'package:intl/intl.dart';
|
|
|
|
class ScheduleScreen extends StatefulWidget {
|
|
const ScheduleScreen({super.key});
|
|
|
|
@override
|
|
State<ScheduleScreen> createState() => _ScheduleScreenState();
|
|
}
|
|
|
|
class _ScheduleScreenState extends State<ScheduleScreen> {
|
|
final dateController = TextEditingController();
|
|
final timeController = TextEditingController();
|
|
|
|
final dbRef = FirebaseDatabase.instance.ref("schedules");
|
|
final firestore = FirebaseFirestore.instance;
|
|
|
|
String? editingKey;
|
|
|
|
/// ================= DATE PICKER =================
|
|
Future<void> pickDate() async {
|
|
final picked = await showDatePicker(
|
|
context: context,
|
|
firstDate: DateTime(2024),
|
|
lastDate: DateTime(2030),
|
|
);
|
|
|
|
if (picked != null) {
|
|
dateController.text =
|
|
DateFormat("dd-MM-yyyy").format(picked);
|
|
}
|
|
}
|
|
|
|
/// ================= TIME PICKER =================
|
|
Future<void> pickTime() async {
|
|
final picked = await showTimePicker(
|
|
context: context,
|
|
initialTime: TimeOfDay.now(),
|
|
);
|
|
|
|
if (picked != null) {
|
|
final now = DateTime.now();
|
|
final formattedTime = DateFormat("HH:mm").format(
|
|
DateTime(
|
|
now.year,
|
|
now.month,
|
|
now.day,
|
|
picked.hour,
|
|
picked.minute,
|
|
),
|
|
);
|
|
|
|
timeController.text = formattedTime;
|
|
}
|
|
}
|
|
|
|
/// ================= SAVE =================
|
|
Future<void> saveSchedule() async {
|
|
final date = dateController.text;
|
|
final time = timeController.text;
|
|
|
|
if (date.isEmpty || time.isEmpty) return;
|
|
|
|
Map<String, dynamic> data = {
|
|
"date": date,
|
|
"time": time,
|
|
"status": "idle",
|
|
"type": "single",
|
|
};
|
|
|
|
if (editingKey == null) {
|
|
final newRef = dbRef.push();
|
|
await newRef.set(data);
|
|
} else {
|
|
await dbRef.child(editingKey!).update(data);
|
|
editingKey = null;
|
|
}
|
|
|
|
dateController.clear();
|
|
timeController.clear();
|
|
|
|
setState(() {});
|
|
}
|
|
|
|
/// ================= DELETE =================
|
|
Future<void> deleteSchedule(String key) async {
|
|
await dbRef.child(key).remove();
|
|
}
|
|
|
|
/// ================= EDIT =================
|
|
void editSchedule(String key, String date, String time) {
|
|
setState(() {
|
|
editingKey = key;
|
|
dateController.text = date;
|
|
timeController.text = time;
|
|
});
|
|
}
|
|
|
|
/// ================= START =================
|
|
Future<void> startWatering(
|
|
String key, String date, String time) async {
|
|
await dbRef.child(key).update({"status": "watering"});
|
|
|
|
final docId = "${date}_$time";
|
|
|
|
/// START LOG
|
|
await firestore.collection("watering_history").doc(docId).set({
|
|
"date": date,
|
|
"startTime": time,
|
|
"endTime": null,
|
|
"status": "watering",
|
|
"type": "single",
|
|
"timestamp": FieldValue.serverTimestamp(),
|
|
});
|
|
|
|
/// simulasi pompa 5 detik
|
|
await Future.delayed(const Duration(seconds: 5));
|
|
|
|
final endTime =
|
|
DateFormat("HH:mm").format(DateTime.now());
|
|
|
|
await firestore
|
|
.collection("watering_history")
|
|
.doc(docId)
|
|
.update({
|
|
"endTime": endTime,
|
|
"status": "done",
|
|
});
|
|
|
|
await dbRef.child(key).update({"status": "done"});
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
dbRef.onChildChanged.listen((event) async {
|
|
final data = event.snapshot.value as Map;
|
|
|
|
final date = data["date"];
|
|
final time = data["time"];
|
|
|
|
final docId = "${date}_$time";
|
|
|
|
if (data["status"] == "watering") {
|
|
await firestore
|
|
.collection("watering_history")
|
|
.doc(docId)
|
|
.set({
|
|
"date": date,
|
|
"startTime": time,
|
|
"endTime": null,
|
|
"status": "watering",
|
|
"type": "single",
|
|
"timestamp": FieldValue.serverTimestamp(),
|
|
});
|
|
}
|
|
|
|
if (data["status"] == "done") {
|
|
final endTime =
|
|
DateFormat("HH:mm").format(DateTime.now());
|
|
|
|
await firestore
|
|
.collection("watering_history")
|
|
.doc(docId)
|
|
.update({
|
|
"endTime": endTime,
|
|
"status": "done",
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/// ================= UI =================
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: const Color(0xFFEFF2EF),
|
|
body: SafeArea(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
children: [
|
|
/// HEADER
|
|
Row(
|
|
children: [
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
shape: BoxShape.circle,
|
|
boxShadow: const [
|
|
BoxShadow(
|
|
blurRadius: 5,
|
|
color: Colors.black12)
|
|
],
|
|
),
|
|
child: IconButton(
|
|
icon: const Icon(Icons.arrow_back),
|
|
onPressed: () => Navigator.pop(context),
|
|
),
|
|
),
|
|
const SizedBox(width: 15),
|
|
const Text(
|
|
"Atur Jadwal Penyiraman",
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold),
|
|
)
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 20),
|
|
|
|
/// FORM
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(20),
|
|
color: Colors.white,
|
|
boxShadow: const [
|
|
BoxShadow(
|
|
blurRadius: 8,
|
|
color: Colors.black12,
|
|
offset: Offset(2, 4))
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text("Tanggal"),
|
|
const SizedBox(height: 8),
|
|
TextField(
|
|
controller: dateController,
|
|
readOnly: true,
|
|
onTap: pickDate,
|
|
decoration: const InputDecoration(
|
|
hintText: "Pilih tanggal",
|
|
suffixIcon: Icon(Icons.calendar_today),
|
|
border: OutlineInputBorder(),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
const Text("Jam"),
|
|
const SizedBox(height: 8),
|
|
TextField(
|
|
controller: timeController,
|
|
readOnly: true,
|
|
onTap: pickTime,
|
|
decoration: const InputDecoration(
|
|
hintText: "Pilih jam",
|
|
suffixIcon: Icon(Icons.access_time),
|
|
border: OutlineInputBorder(),
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
Center(
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor:
|
|
const Color(0xFF34A853),
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 40, vertical: 12),
|
|
),
|
|
onPressed: saveSchedule,
|
|
child: Text(
|
|
editingKey == null
|
|
? "Simpan"
|
|
: "Update"),
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 20),
|
|
|
|
/// LIST
|
|
Expanded(
|
|
child: StreamBuilder(
|
|
stream: dbRef.onValue,
|
|
builder: (context, snapshot) {
|
|
if (!snapshot.hasData ||
|
|
snapshot.data!.snapshot.value ==
|
|
null) {
|
|
return const Center(
|
|
child: Text("Belum ada jadwal"));
|
|
}
|
|
|
|
Map data =
|
|
snapshot.data!.snapshot.value
|
|
as Map;
|
|
|
|
return ListView(
|
|
children:
|
|
data.entries.map<Widget>((entry) {
|
|
final key = entry.key;
|
|
final value = entry.value;
|
|
|
|
return Card(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius:
|
|
BorderRadius.circular(20),
|
|
),
|
|
child: ListTile(
|
|
title: Text(
|
|
"Jam ${value['time']} WIB"),
|
|
subtitle: Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.start,
|
|
children: [
|
|
Text(value['date']),
|
|
Text(
|
|
"Status: ${value['status']}",
|
|
style: TextStyle(
|
|
color: value['status'] ==
|
|
"watering"
|
|
? Colors.green
|
|
: value['status'] ==
|
|
"done"
|
|
? Colors.blue
|
|
: Colors.grey,
|
|
),
|
|
)
|
|
],
|
|
),
|
|
trailing: Row(
|
|
mainAxisSize:
|
|
MainAxisSize.min,
|
|
children: [
|
|
IconButton(
|
|
icon: const Icon(Icons.edit,
|
|
color: Colors.orange),
|
|
onPressed: () {
|
|
editSchedule(
|
|
key,
|
|
value['date'],
|
|
value['time'],
|
|
);
|
|
},
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.delete,
|
|
color: Colors.red),
|
|
onPressed: () {
|
|
deleteSchedule(key);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}).toList(),
|
|
);
|
|
},
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
} |