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