main
This commit is contained in:
commit
864ec675fd
|
@ -0,0 +1,90 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
|
import 'screens/realtime_data_screen.dart';
|
||||||
|
import 'screens/pump_schedule_screen.dart';
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (kIsWeb) {
|
||||||
|
await Firebase.initializeApp(
|
||||||
|
options: const FirebaseOptions(
|
||||||
|
apiKey: "AIzaSyBCe-30GPJRp0p7psDH4t2D01WHPcSNIqQ",
|
||||||
|
authDomain: "apkrafi.firebaseapp.com",
|
||||||
|
databaseURL: "https://apkrafi-default-rtdb.firebaseio.com",
|
||||||
|
projectId: "apkrafi",
|
||||||
|
storageBucket: "apkrafi.appspot.com",
|
||||||
|
messagingSenderId: "121136047777",
|
||||||
|
appId: "1:121136047777:web:306ae9d0b88b7c33c432e0",
|
||||||
|
measurementId: "G-L31DDT24WJ"),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await Firebase.initializeApp();
|
||||||
|
}
|
||||||
|
print('Firebase initialized successfully');
|
||||||
|
} catch (e) {
|
||||||
|
print('Error initializing Firebase: $e');
|
||||||
|
}
|
||||||
|
|
||||||
|
runApp(const MyApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyApp extends StatelessWidget {
|
||||||
|
const MyApp({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
title: 'Smart Garden Monitor',
|
||||||
|
theme: ThemeData(
|
||||||
|
primarySwatch: Colors.green,
|
||||||
|
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||||
|
),
|
||||||
|
home: const MainScreen(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MainScreen extends StatefulWidget {
|
||||||
|
const MainScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MainScreen> createState() => _MainScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MainScreenState extends State<MainScreen> {
|
||||||
|
int _selectedIndex = 0;
|
||||||
|
|
||||||
|
final List<Widget> _screens = [
|
||||||
|
const RealtimeDataScreen(),
|
||||||
|
const PumpScheduleScreen(),
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: _screens[_selectedIndex],
|
||||||
|
bottomNavigationBar: BottomNavigationBar(
|
||||||
|
currentIndex: _selectedIndex,
|
||||||
|
onTap: (index) {
|
||||||
|
setState(() {
|
||||||
|
_selectedIndex = index;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
items: const [
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.sensors),
|
||||||
|
label: 'Sensor',
|
||||||
|
),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
icon: Icon(Icons.schedule),
|
||||||
|
label: 'Jadwal',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,348 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
|
|
||||||
|
class PumpControlScreen extends StatefulWidget {
|
||||||
|
const PumpControlScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PumpControlScreen> createState() => _PumpControlScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PumpControlScreenState extends State<PumpControlScreen> {
|
||||||
|
final DatabaseReference _database = FirebaseDatabase.instance.ref();
|
||||||
|
bool pump3Enabled = false;
|
||||||
|
bool isLoading = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadPumpStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _loadPumpStatus() {
|
||||||
|
_database.child('schedule/pump3Enabled').onValue.listen((event) {
|
||||||
|
if (event.snapshot.value != null) {
|
||||||
|
setState(() {
|
||||||
|
pump3Enabled = event.snapshot.value as bool;
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
|
print(
|
||||||
|
'Pump status updated: pump3Enabled = $pump3Enabled'); // Debug print
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _togglePump3(bool value) async {
|
||||||
|
try {
|
||||||
|
await _database.child('schedule').update({
|
||||||
|
'pump3Enabled': value,
|
||||||
|
});
|
||||||
|
print('Updated pump3 status: $value'); // Debug print
|
||||||
|
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Pompa 3 ${value ? "diaktifkan" : "dinonaktifkan"}'),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print('Error updating pump status: $e'); // Debug print
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Error: ${e.toString()}'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPump2Card() {
|
||||||
|
return Card(
|
||||||
|
elevation: 4,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [Colors.grey.shade200, Colors.grey.shade100],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.water,
|
||||||
|
color: Colors.grey,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
'Pompa 2',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.power_off,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
'Pompa Mati',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPump3Card() {
|
||||||
|
return Card(
|
||||||
|
elevation: 4,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [Colors.blue.shade200, Colors.blue.shade100],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.water,
|
||||||
|
color: Colors.blue,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
'Pompa 3',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: pump3Enabled
|
||||||
|
? Colors.blue.withOpacity(0.1)
|
||||||
|
: Colors.grey.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
pump3Enabled ? Icons.power : Icons.power_off,
|
||||||
|
color: pump3Enabled ? Colors.blue : Colors.grey,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
pump3Enabled ? 'Pompa Menyala' : 'Pompa Mati',
|
||||||
|
style: TextStyle(
|
||||||
|
color: pump3Enabled ? Colors.blue : Colors.grey,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPump4Card() {
|
||||||
|
return Card(
|
||||||
|
elevation: 4,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
!pump3Enabled ? Colors.green.shade200 : Colors.grey.shade200,
|
||||||
|
!pump3Enabled ? Colors.green.shade100 : Colors.grey.shade100
|
||||||
|
],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.water,
|
||||||
|
color: !pump3Enabled ? Colors.green : Colors.grey,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
'Pompa 4',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: !pump3Enabled ? Colors.green : Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: !pump3Enabled
|
||||||
|
? Colors.green.withOpacity(0.1)
|
||||||
|
: Colors.grey.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
!pump3Enabled ? Icons.power : Icons.power_off,
|
||||||
|
color: !pump3Enabled ? Colors.green : Colors.grey,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Text(
|
||||||
|
!pump3Enabled ? 'Pompa Menyala' : 'Pompa Mati',
|
||||||
|
style: TextStyle(
|
||||||
|
color: !pump3Enabled ? Colors.green : Colors.grey,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Kontrol Pompa'),
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
),
|
||||||
|
body: isLoading
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: Column(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: RefreshIndicator(
|
||||||
|
onRefresh: () async {
|
||||||
|
setState(() {
|
||||||
|
isLoading = true;
|
||||||
|
});
|
||||||
|
_loadPumpStatus();
|
||||||
|
},
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_buildPump2Card(),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_buildPump3Card(),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_buildPump4Card(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Toggle button at the bottom
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withOpacity(0.2),
|
||||||
|
spreadRadius: 1,
|
||||||
|
blurRadius: 5,
|
||||||
|
offset: const Offset(0, -2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () => _togglePump3(!pump3Enabled),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: pump3Enabled ? Colors.red : Colors.green,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 18),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
),
|
||||||
|
elevation: 4,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
pump3Enabled ? Icons.power_off : Icons.power,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 28,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
pump3Enabled ? 'Matikan Pompa 3' : 'Nyalakan Pompa 3',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,296 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
class PumpScheduleScreen extends StatefulWidget {
|
||||||
|
const PumpScheduleScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PumpScheduleScreen> createState() => _PumpScheduleScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PumpScheduleScreenState extends State<PumpScheduleScreen> {
|
||||||
|
final DatabaseReference _database = FirebaseDatabase.instance.ref();
|
||||||
|
Map<String, dynamic> pumpSchedules = {};
|
||||||
|
bool isLoading = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadPumpSchedules();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _loadPumpSchedules() {
|
||||||
|
_database.child('schedule').onValue.listen((event) {
|
||||||
|
if (event.snapshot.value != null) {
|
||||||
|
setState(() {
|
||||||
|
pumpSchedules =
|
||||||
|
Map<String, dynamic>.from(event.snapshot.value as Map);
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
|
print('Loaded schedules: $pumpSchedules');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatTime(int hour, int minute) {
|
||||||
|
final time = DateTime(2024, 1, 1, hour, minute);
|
||||||
|
return DateFormat('HH:mm').format(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPumpScheduleCard(String pumpId, Map<String, dynamic> schedule) {
|
||||||
|
final pumpSchedule = schedule[pumpId] as Map<dynamic, dynamic>? ?? {};
|
||||||
|
final startHour = pumpSchedule['startHour'] ?? 0;
|
||||||
|
final startMinute = pumpSchedule['startMinute'] ?? 0;
|
||||||
|
final endHour = pumpSchedule['endHour'] ?? 0;
|
||||||
|
final endMinute = pumpSchedule['endMinute'] ?? 0;
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
elevation: 4,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
pumpId == 'pump2'
|
||||||
|
? 'Jadwal Penyiraman'
|
||||||
|
: pumpId == 'pump3'
|
||||||
|
? 'Jadwal Pupuk Pertumbuhan'
|
||||||
|
: pumpId == 'pump4'
|
||||||
|
? 'Jadwal Pupuk Pembuahan'
|
||||||
|
: 'Pompa $pumpId',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Waktu Mulai',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
_formatTime(startHour, startMinute),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Waktu Selesai',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
_formatTime(endHour, endMinute),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: () => _showTimePicker(pumpId, true),
|
||||||
|
icon: const Icon(Icons.edit),
|
||||||
|
label: const Text('Edit Waktu Mulai'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: () => _showTimePicker(pumpId, false),
|
||||||
|
icon: const Icon(Icons.edit),
|
||||||
|
label: const Text('Edit Waktu Selesai'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showTimePicker(String pumpId, bool isStartTime) async {
|
||||||
|
final currentSchedule =
|
||||||
|
pumpSchedules[pumpId] as Map<dynamic, dynamic>? ?? {};
|
||||||
|
final currentHour = isStartTime
|
||||||
|
? currentSchedule['startHour'] ?? 0
|
||||||
|
: currentSchedule['endHour'] ?? 0;
|
||||||
|
final currentMinute = isStartTime
|
||||||
|
? currentSchedule['startMinute'] ?? 0
|
||||||
|
: currentSchedule['endMinute'] ?? 0;
|
||||||
|
|
||||||
|
final TimeOfDay? picked = await showTimePicker(
|
||||||
|
context: context,
|
||||||
|
initialTime: TimeOfDay(hour: currentHour, minute: currentMinute),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (picked != null) {
|
||||||
|
final hour = picked.hour;
|
||||||
|
final minute = picked.minute;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isStartTime) {
|
||||||
|
await _database.child('schedule/$pumpId').update({
|
||||||
|
'startHour': hour,
|
||||||
|
'startMinute': minute,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await _database.child('schedule/$pumpId').update({
|
||||||
|
'endHour': hour,
|
||||||
|
'endMinute': minute,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
'Waktu ${isStartTime ? "mulai" : "selesai"} berhasil diperbarui'),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Error: ${e.toString()}'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Jadwal Pompa'),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
),
|
||||||
|
body: isLoading
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: RefreshIndicator(
|
||||||
|
onRefresh: () async {
|
||||||
|
setState(() {
|
||||||
|
isLoading = true;
|
||||||
|
});
|
||||||
|
_loadPumpSchedules();
|
||||||
|
},
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
_buildPumpScheduleCard('pump2', pumpSchedules),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
if (pumpSchedules.containsKey('pump3Enabled'))
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Jenis Pupuk',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
ToggleButtons(
|
||||||
|
isSelected: [
|
||||||
|
!(pumpSchedules['pump3Enabled'] ?? false), // false = pembuahan
|
||||||
|
(pumpSchedules['pump3Enabled'] ?? false), // true = pertumbuhan
|
||||||
|
],
|
||||||
|
onPressed: (index) async {
|
||||||
|
final newValue = index == 1; // index 1 = pertumbuhan
|
||||||
|
try {
|
||||||
|
await _database.child('schedule').update({
|
||||||
|
'pump3Enabled': newValue,
|
||||||
|
});
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
newValue
|
||||||
|
? 'Mode diubah ke Pupuk Pertumbuhan'
|
||||||
|
: 'Mode diubah ke Pupuk Pembuahan',
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Error: ${e.toString()}'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
selectedColor: Colors.white,
|
||||||
|
fillColor: Colors.green,
|
||||||
|
color: Colors.green,
|
||||||
|
children: const [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
child: Text('Pembuahan'),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
child: Text('Pertumbuhan'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_buildPumpScheduleCard('pump3', pumpSchedules),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_buildPumpScheduleCard('pump4', pumpSchedules),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,301 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
|
|
||||||
|
class RealtimeDataScreen extends StatefulWidget {
|
||||||
|
const RealtimeDataScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<RealtimeDataScreen> createState() => _RealtimeDataScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RealtimeDataScreenState extends State<RealtimeDataScreen> {
|
||||||
|
final DatabaseReference _database = FirebaseDatabase.instance.ref();
|
||||||
|
|
||||||
|
Map<String, dynamic> sensorData = {
|
||||||
|
'humidity': 0,
|
||||||
|
'soilMoisture': 0,
|
||||||
|
'temperature': 0,
|
||||||
|
};
|
||||||
|
bool isLoading = true;
|
||||||
|
|
||||||
|
late Timer _imageRefreshTimer;
|
||||||
|
String _imageUrl = '';
|
||||||
|
String? _scheduledTime;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_setupRealtimeListener();
|
||||||
|
_startImageAutoRefresh();
|
||||||
|
_fetchSchedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setupRealtimeListener() {
|
||||||
|
_database.child('sensors').onValue.listen((event) {
|
||||||
|
if (event.snapshot.value != null) {
|
||||||
|
setState(() {
|
||||||
|
sensorData = Map<String, dynamic>.from(event.snapshot.value as Map);
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startImageAutoRefresh() {
|
||||||
|
_refreshImageUrl();
|
||||||
|
_imageRefreshTimer = Timer.periodic(const Duration(seconds: 10), (timer) {
|
||||||
|
_refreshImageUrl();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _refreshImageUrl() {
|
||||||
|
setState(() {
|
||||||
|
_imageUrl =
|
||||||
|
'https://nwlikynouhddjwyjrwnl.supabase.co/storage/v1/object/public/photo/latest.jpg?t=${DateTime.now().millisecondsSinceEpoch}';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _fetchSchedule() async {
|
||||||
|
final snapshot = await _database.child('camera/schedule').get();
|
||||||
|
if (snapshot.exists) {
|
||||||
|
setState(() {
|
||||||
|
_scheduledTime = snapshot.value.toString().trim().isEmpty
|
||||||
|
? null
|
||||||
|
: snapshot.value.toString();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _pickSchedule() async {
|
||||||
|
final TimeOfDay? picked = await showTimePicker(
|
||||||
|
context: context,
|
||||||
|
initialTime: TimeOfDay.now(),
|
||||||
|
builder: (context, child) {
|
||||||
|
return MediaQuery(
|
||||||
|
data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true),
|
||||||
|
child: child!,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (picked != null) {
|
||||||
|
final hour = picked.hour.toString().padLeft(2, '0');
|
||||||
|
final minute = picked.minute.toString().padLeft(2, '0');
|
||||||
|
final formatted = '$hour.$minute';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await _database.child('camera/schedule').set(formatted);
|
||||||
|
setState(() {
|
||||||
|
_scheduledTime = formatted;
|
||||||
|
});
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Gambar dijadwalkan pada $formatted'),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Gagal menjadwalkan: $e'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_imageRefreshTimer.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Data Sensor & Kamera'),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
),
|
||||||
|
body: isLoading
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: RefreshIndicator(
|
||||||
|
onRefresh: () async {
|
||||||
|
setState(() {
|
||||||
|
isLoading = true;
|
||||||
|
});
|
||||||
|
_setupRealtimeListener();
|
||||||
|
await _fetchSchedule();
|
||||||
|
},
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildSensorCard(
|
||||||
|
'Kelembaban Udara',
|
||||||
|
'${sensorData['humidity']?.toStringAsFixed(1) ?? 'N/A'}%',
|
||||||
|
Icons.water,
|
||||||
|
Colors.blue,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_buildSensorCard(
|
||||||
|
'Kelembaban Tanah',
|
||||||
|
'${sensorData['soilMoisture']?.toStringAsFixed(1) ?? 'N/A'}%',
|
||||||
|
Icons.water_drop,
|
||||||
|
Colors.lightBlue,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_buildSensorCard(
|
||||||
|
'Suhu',
|
||||||
|
'${sensorData['temperature']?.toStringAsFixed(1) ?? 'N/A'}°C',
|
||||||
|
Icons.thermostat,
|
||||||
|
Colors.orange,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Combined Container: Jadwal + Lihat Gambar
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.orange.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Jadwal Pengambilan Gambar:',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
_scheduledTime ?? 'Belum ada jadwal',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 24, color: Colors.black87),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
icon: const Icon(Icons.schedule),
|
||||||
|
label:
|
||||||
|
const Text('Atur Jadwal Pengambilan'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.orange,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 14, horizontal: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: _pickSchedule,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
icon: const Icon(Icons.image),
|
||||||
|
label: const Text('Lihat Gambar Terbaru'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 14, horizontal: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
_refreshImageUrl();
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: const Text('Gambar Terbaru'),
|
||||||
|
content: Image.network(
|
||||||
|
_imageUrl,
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
key: ValueKey(_imageUrl),
|
||||||
|
errorBuilder:
|
||||||
|
(context, error, stackTrace) =>
|
||||||
|
const Text(
|
||||||
|
'Gagal memuat gambar.'),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () =>
|
||||||
|
Navigator.of(context).pop(),
|
||||||
|
child: const Text('Tutup'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSensorCard(
|
||||||
|
String title, String value, IconData icon, Color color) {
|
||||||
|
return Card(
|
||||||
|
elevation: 4,
|
||||||
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(icon, color: color, size: 32),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue