443 lines
13 KiB
Dart
443 lines
13 KiB
Dart
import 'dart:ui';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:firebase_core/firebase_core.dart';
|
|
import 'package:firebase_database/firebase_database.dart';
|
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
import 'package:flutter_background_service/flutter_background_service.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'dart:async';
|
|
|
|
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
|
FlutterLocalNotificationsPlugin();
|
|
|
|
void main() async {
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|
await Firebase.initializeApp(
|
|
options: const FirebaseOptions(
|
|
databaseURL: 'https://rifky-ea47a-default-rtdb.firebaseio.com',
|
|
apiKey:
|
|
'AIzaSyCtV4taj9jDIY31GtedIWmS9z5zP8JPmmY', // You'll need to add your API key here
|
|
appId:
|
|
'1:381205401183:web:2d37bb3d8cb686227115ea', // You need to get this from Firebase Console
|
|
messagingSenderId:
|
|
'381205401183', // You'll need to add your Sender ID here
|
|
projectId: 'rifky-ea47a', // Your project ID
|
|
),
|
|
);
|
|
await initializeService();
|
|
runApp(const MyApp());
|
|
}
|
|
|
|
Future<void> initializeService() async {
|
|
final service = FlutterBackgroundService();
|
|
|
|
/// OPTIONAL, using custom notification channel id
|
|
const AndroidNotificationChannel channel = AndroidNotificationChannel(
|
|
'my_foreground', // id
|
|
'MY FOREGROUND SERVICE', // title
|
|
description:
|
|
'This channel is used for important notifications.', // description
|
|
importance: Importance.low, // importance must be at low or higher level
|
|
);
|
|
|
|
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
|
FlutterLocalNotificationsPlugin();
|
|
|
|
const AndroidInitializationSettings initializationSettingsAndroid =
|
|
AndroidInitializationSettings('@mipmap/ic_launcher'); // TANPA .png
|
|
|
|
const InitializationSettings initializationSettings = InitializationSettings(
|
|
android: initializationSettingsAndroid,
|
|
);
|
|
|
|
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
|
|
|
|
await flutterLocalNotificationsPlugin
|
|
.resolvePlatformSpecificImplementation<
|
|
AndroidFlutterLocalNotificationsPlugin>()
|
|
?.createNotificationChannel(channel);
|
|
|
|
await service.configure(
|
|
androidConfiguration: AndroidConfiguration(
|
|
// this will be executed when app is in foreground or background in separated isolate
|
|
onStart: onStart,
|
|
|
|
// auto start service
|
|
autoStart: true,
|
|
isForegroundMode: true,
|
|
|
|
notificationChannelId: 'my_foreground',
|
|
initialNotificationTitle: 'Jamur',
|
|
initialNotificationContent: 'Jamur Sedang Berjalan',
|
|
foregroundServiceNotificationId: 888,
|
|
|
|
foregroundServiceTypes: [AndroidForegroundType.location],
|
|
),
|
|
iosConfiguration: IosConfiguration(
|
|
// auto start service
|
|
autoStart: true,
|
|
|
|
// this will be executed when app is in foreground in separated isolate
|
|
onForeground: onStart,
|
|
|
|
// you have to enable background fetch capability on xcode project
|
|
onBackground: onIosBackground,
|
|
),
|
|
);
|
|
}
|
|
|
|
// to ensure this is executed
|
|
// run app from xcode, then from xcode menu, select Simulate Background Fetch
|
|
|
|
@pragma('vm:entry-point')
|
|
Future<bool> onIosBackground(ServiceInstance service) async {
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|
DartPluginRegistrant.ensureInitialized();
|
|
|
|
SharedPreferences preferences = await SharedPreferences.getInstance();
|
|
await preferences.reload();
|
|
final log = preferences.getStringList('log') ?? <String>[];
|
|
log.add(DateTime.now().toIso8601String());
|
|
await preferences.setStringList('log', log);
|
|
|
|
return true;
|
|
}
|
|
|
|
@pragma('vm:entry-point')
|
|
void onStart(ServiceInstance service) async {
|
|
DartPluginRegistrant.ensureInitialized();
|
|
await Firebase.initializeApp();
|
|
|
|
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
|
FlutterLocalNotificationsPlugin();
|
|
|
|
final DatabaseReference _database = FirebaseDatabase.instance.ref();
|
|
|
|
double? lastTemp;
|
|
double? lastHumidity;
|
|
|
|
// Listener suhu
|
|
_database.child('Temperature').onValue.listen((event) async {
|
|
final data = event.snapshot.value;
|
|
if (data != null) {
|
|
final double temp = double.tryParse(data.toString()) ?? 0.0;
|
|
|
|
if (temp >= 30 && (lastTemp == null || lastTemp! < 30)) {
|
|
await showNotification(
|
|
'Suhu Tinggi!',
|
|
'Suhu sekarang $temp°C. Aktifkan kipas!',
|
|
);
|
|
}
|
|
|
|
lastTemp = temp;
|
|
}
|
|
});
|
|
|
|
// Listener kelembaban
|
|
_database.child('Humidity').onValue.listen((event) async {
|
|
final data = event.snapshot.value;
|
|
if (data != null) {
|
|
final double humidity = double.tryParse(data.toString()) ?? 0.0;
|
|
|
|
if (humidity <= 70 && (lastHumidity == null || lastHumidity! > 70)) {
|
|
await showNotification(
|
|
'Kelembaban Rendah!',
|
|
'Humidity sekarang $humidity%. Periksa kelembaban ruangan.',
|
|
);
|
|
}
|
|
|
|
lastHumidity = humidity;
|
|
}
|
|
});
|
|
|
|
// Background service commands
|
|
if (service is AndroidServiceInstance) {
|
|
service.on('setAsForeground').listen((event) {
|
|
service.setAsForegroundService();
|
|
});
|
|
|
|
service.on('setAsBackground').listen((event) {
|
|
service.setAsBackgroundService();
|
|
});
|
|
}
|
|
|
|
service.on('stopService').listen((event) {
|
|
service.stopSelf();
|
|
});
|
|
}
|
|
|
|
Future<void> showNotification(String title, String body) async {
|
|
const androidDetails = AndroidNotificationDetails(
|
|
'temp_channel_id',
|
|
'Temperature Alerts',
|
|
channelDescription: 'Notification for temperature threshold',
|
|
importance: Importance.max,
|
|
priority: Priority.high,
|
|
showWhen: false,
|
|
);
|
|
const platformDetails = NotificationDetails(android: androidDetails);
|
|
|
|
await flutterLocalNotificationsPlugin.show(
|
|
0,
|
|
title,
|
|
body,
|
|
platformDetails,
|
|
);
|
|
}
|
|
|
|
class MyApp extends StatelessWidget {
|
|
const MyApp({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp(
|
|
title: 'Monitoring App',
|
|
theme: ThemeData(
|
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
|
|
useMaterial3: true,
|
|
),
|
|
home: const MonitoringScreen(),
|
|
);
|
|
}
|
|
}
|
|
|
|
class MonitoringScreen extends StatefulWidget {
|
|
const MonitoringScreen({super.key});
|
|
|
|
@override
|
|
State<MonitoringScreen> createState() => _MonitoringScreenState();
|
|
}
|
|
|
|
class _MonitoringScreenState extends State<MonitoringScreen> {
|
|
final DatabaseReference _database = FirebaseDatabase.instance.ref();
|
|
late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
|
|
|
|
Map<String, dynamic> _monitoringData = {
|
|
'Fan': 'OFF',
|
|
'Humidity': 0.0,
|
|
'Pump': 'OFF',
|
|
'Temperature': 0.0,
|
|
};
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
//_initializeNotifications(); // Inisialisasi notifikasi
|
|
_setupRealtimeUpdates();
|
|
}
|
|
|
|
// void _initializeNotifications() {
|
|
// flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
|
|
|
// const AndroidInitializationSettings initializationSettingsAndroid =
|
|
// AndroidInitializationSettings('@mipmap/ic_launcher');
|
|
|
|
// const InitializationSettings initializationSettings =
|
|
// InitializationSettings(android: initializationSettingsAndroid);
|
|
|
|
// flutterLocalNotificationsPlugin.initialize(initializationSettings);
|
|
// }
|
|
|
|
// Future<void> _showNotification(String title, String body) async {
|
|
// const AndroidNotificationDetails androidPlatformChannelSpecifics =
|
|
// AndroidNotificationDetails(
|
|
// 'temp_channel_id',
|
|
// 'Temperature Alerts',
|
|
// importance: Importance.max,
|
|
// priority: Priority.high,
|
|
// showWhen: false,
|
|
// );
|
|
// const NotificationDetails platformChannelSpecifics =
|
|
// NotificationDetails(android: androidPlatformChannelSpecifics);
|
|
|
|
// await flutterLocalNotificationsPlugin.show(
|
|
// 0, // Notification ID
|
|
// title,
|
|
// body,
|
|
// platformChannelSpecifics,
|
|
// );
|
|
// }
|
|
|
|
void _setupRealtimeUpdates() {
|
|
_database.onValue.listen((DatabaseEvent event) {
|
|
if (event.snapshot.value != null) {
|
|
setState(() {
|
|
_monitoringData =
|
|
Map<String, dynamic>.from(event.snapshot.value as Map);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
void _toggleDevice(String device) {
|
|
final currentStatus = _monitoringData[device] as String;
|
|
final newStatus = currentStatus == 'ON' ? 'OFF' : 'ON';
|
|
_database.update({device: newStatus});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Monitoring & Control Dashboard'),
|
|
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
|
),
|
|
body: SingleChildScrollView(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
const Text(
|
|
'Monitoring',
|
|
style: TextStyle(
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildMonitoringCard(
|
|
'Temperature',
|
|
'${_monitoringData['Temperature']}°C',
|
|
Icons.thermostat,
|
|
Colors.red,
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: _buildMonitoringCard(
|
|
'Humidity',
|
|
'${_monitoringData['Humidity']}%',
|
|
Icons.water_drop,
|
|
Colors.blue,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 32),
|
|
const Text(
|
|
'Control',
|
|
style: TextStyle(
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildControlCard(
|
|
'Fan',
|
|
_monitoringData['Fan'] ?? 'OFF',
|
|
Icons.air,
|
|
Colors.green,
|
|
() => _toggleDevice('Fan'),
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: _buildControlCard(
|
|
'Pump',
|
|
_monitoringData['Pump'] ?? 'OFF',
|
|
Icons.water,
|
|
Colors.orange,
|
|
() => _toggleDevice('Pump'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildMonitoringCard(
|
|
String title, String value, IconData icon, Color color) {
|
|
return Card(
|
|
elevation: 4,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
children: [
|
|
Icon(icon, size: 40, color: color),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
title,
|
|
style: const TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.bold,
|
|
color: color,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildControlCard(String title, String status, IconData icon,
|
|
Color color, VoidCallback onTap) {
|
|
return Card(
|
|
elevation: 4,
|
|
child: InkWell(
|
|
onTap: onTap,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
children: [
|
|
Icon(icon, size: 40, color: color),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
title,
|
|
style: const TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Container(
|
|
padding:
|
|
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: status == 'ON' ? Colors.green : Colors.red,
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Text(
|
|
status,
|
|
style: const TextStyle(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Tap to toggle',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: Colors.grey[600],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|