import 'package:get/get.dart'; import 'package:flutter/material.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:url_launcher/url_launcher.dart'; import 'dart:async'; import '../../../services/firestore_service.dart'; import '../../../services/realtime_database_service.dart'; import '../../../services/notification_service.dart'; import '../../../models/patient_model.dart'; import '../../../models/chart_data_point.dart'; import '../../../models/history_model.dart'; import '../../../models/notification_model.dart'; import '../../../models/realtime_monitoring_model.dart'; import '../../../widgets/app_snackbar.dart'; class InfusData { final String dropsPerMinute; final String room; final String deviceId; final String updateTime; final String deviceStatus; InfusData({ required this.dropsPerMinute, required this.room, required this.deviceId, required this.updateTime, required this.deviceStatus, }); } class NotificationData { final String id; final String title; final String message; final String room; final String deviceId; final String timeAgo; final DateTime createdAt; NotificationData({ required this.id, required this.title, required this.message, required this.room, required this.deviceId, required this.timeAgo, required this.createdAt, }); } class HomePatientController extends GetxController { final FirestoreService _firestoreService = FirestoreService(); final RealtimeDatabaseService _realtimeService = RealtimeDatabaseService(); final NotificationService _notificationService = NotificationService(); final deviceId = ''.obs; final roomId = ''.obs; final patientName = 'Patient'.obs; final userEmail = ''.obs; final currentCarouselIndex = 0.obs; final bannerImages = [ 'assets/images/banner-slider3.png', 'assets/images/banner-slider4.png', ]; final currentInfusData = InfusData( dropsPerMinute: '0', room: '-', deviceId: '-', updateTime: '-', deviceStatus: 'disconnected', ).obs; final isBuzzerActive = false.obs; final chartDataPoints = [].obs; final notificationsList = [].obs; final isLoading = true.obs; final isLoadingHistory = true.obs; final isLoadingNotifications = true.obs; StreamSubscription? _realtimeSubscription; StreamSubscription? _notificationsSubscription; StreamSubscription? _historySubscription; final Set _alertedDevices = {}; @override void onInit() { super.onInit(); _initializeNotificationService(); _loadDataFromArguments(); } @override void onReady() { super.onReady(); } @override void onClose() { _realtimeSubscription?.cancel(); _notificationsSubscription?.cancel(); _historySubscription?.cancel(); super.onClose(); } Future _initializeNotificationService() async { try { await _notificationService.initialize(); print('Notification service initialized'); } catch (e) { print('Error initializing notification service: $e'); } } void _loadDataFromArguments() async { try { final args = Get.arguments as Map?; if (args != null) { deviceId.value = args['deviceId'] ?? ''; patientName.value = args['userName'] ?? 'Patient'; userEmail.value = args['userEmail'] ?? ''; } if (deviceId.value.isEmpty) { AppSnackbar.error('Device ID tidak ditemukan'); isLoading.value = false; return; } await _loadPatientData(); if (roomId.value.isNotEmpty) { _startRealtimeMonitoring(); _loadHistoryData(); _loadNotifications(); } else { AppSnackbar.error('Room ID tidak ditemukan'); isLoading.value = false; } } catch (e) { AppSnackbar.error('Gagal memuat data: $e'); isLoading.value = false; } } Future _loadPatientData() async { try { isLoading.value = true; final patient = await _firestoreService.getPatientByDeviceId( deviceId.value, ); if (patient != null) { patientName.value = patient.namePatient; roomId.value = patient.roomId; final room = await _firestoreService.getRoomById(patient.roomId); if (currentInfusData.value.deviceStatus == 'disconnected') { currentInfusData.value = InfusData( dropsPerMinute: '0', room: room?.roomName ?? patient.roomId, deviceId: patient.deviceId, updateTime: 'Loading...', deviceStatus: 'disconnected', ); } } else { AppSnackbar.error('Data pasien tidak ditemukan'); } } catch (e) { AppSnackbar.error('Gagal memuat data pasien: $e'); print('Error loading patient data: $e'); } finally { isLoading.value = false; } } void _startRealtimeMonitoring() { try { if (roomId.value.isEmpty || deviceId.value.isEmpty) { print('Room ID or Device ID is empty'); return; } final dbRoomId = roomId.value.toLowerCase().replaceAll(' ', '_'); _realtimeSubscription?.cancel(); _realtimeSubscription = _realtimeService .getDeviceStream(dbRoomId, deviceId.value) .listen( (device) async { if (device != null) { await _updateCurrentInfusData(device); await _checkDropRate(device); isBuzzerActive.value = device.buzzerActive; } else { print('Received null device data'); } }, onError: (error) { print('Realtime stream error: $error'); AppSnackbar.error('Koneksi realtime terputus'); }, cancelOnError: false, ); } catch (e) { print('Error starting monitoring: $e'); AppSnackbar.error('Gagal memulai monitoring: $e'); } } Future _updateCurrentInfusData(RealtimeMonitoringModel device) async { try { final room = await _firestoreService.getRoomById(device.roomId); currentInfusData.value = InfusData( dropsPerMinute: '${device.dropRate.toStringAsFixed(0)}', room: room?.roomName ?? device.roomId, deviceId: device.deviceId, updateTime: _getLastUpdateText(device.lastUpdate), deviceStatus: device.deviceStatus, ); } catch (e) { print('Error updating infus data: $e'); AppSnackbar.error('Gagal update data: $e'); } } Future _checkDropRate(RealtimeMonitoringModel device) async { try { final deviceId = device.deviceId; final dropRate = device.dropRate; final deviceStatus = device.deviceStatus; if (dropRate == 0.0 && deviceStatus == 'connected') { if (!_alertedDevices.contains(deviceId)) { final patient = await _firestoreService.getPatientByDeviceId( deviceId, ); final room = await _firestoreService.getRoomById(device.roomId); if (patient != null) { await _notificationService.showDropRateAlertForPatient( patientId: patient.id, patientName: patient.namePatient, deviceId: deviceId, roomName: room?.roomName ?? device.roomId, ); _alertedDevices.add(deviceId); print('Alert sent for device: $deviceId'); } } } else if (dropRate > 0.0) { if (_alertedDevices.contains(deviceId)) { _alertedDevices.remove(deviceId); _notificationService.clearCooldown(deviceId); await _notificationService.cancelNotificationByDeviceId(deviceId); print('Device reset and notification dismissed: $deviceId'); } } } catch (e) { print('Error checking drop rate: $e'); } } Future turnOffBuzzer() async { try { final dbRoomId = roomId.value.toLowerCase().replaceAll(' ', '_'); await _realtimeService.controlBuzzer(dbRoomId, deviceId.value, false); isBuzzerActive.value = false; AppSnackbar.success('Buzzer dimatikan'); } catch (e) { print('Error turning off buzzer: $e'); AppSnackbar.error('Gagal mematikan buzzer'); } } void _loadHistoryData() { try { isLoadingHistory.value = true; _historySubscription?.cancel(); _historySubscription = _firestoreService .getDeviceHistoriesStream(deviceId.value) .listen( (histories) { try { if (histories.isNotEmpty) { _processHistoryData(histories); } else { chartDataPoints.value = []; } isLoadingHistory.value = false; } catch (e) { print('Error processing history: $e'); chartDataPoints.value = []; isLoadingHistory.value = false; } }, onError: (error) { print('History stream error: $error'); chartDataPoints.value = []; isLoadingHistory.value = false; AppSnackbar.error('Gagal memuat history: $error'); }, cancelOnError: false, ); } catch (e) { print('Error loading history: $e'); isLoadingHistory.value = false; AppSnackbar.error('Gagal memuat history: $e'); } } void _processHistoryData(List histories) { try { // Ambil 10 data TERBARU histories.sort((a, b) => b.timestamp.compareTo(a.timestamp)); final latestHistories = histories.take(10).toList(); // Urutkan lagi dari lama -> baru supaya grafik terbaca kiri ke kanan latestHistories.sort((a, b) => a.timestamp.compareTo(b.timestamp)); final List points = latestHistories.map((history) { return ChartDataPoint( timestamp: history.timestamp, dropsPerMinute: history.dropPerMinute, ); }).toList(); chartDataPoints.value = points; } catch (e) { print('Error processing history data: $e'); chartDataPoints.value = []; } } void _loadNotifications() { try { isLoadingNotifications.value = true; _notificationsSubscription?.cancel(); _notificationsSubscription = _firestoreService .getNotificationsStream() .listen( (notifications) async { try { final filteredNotifications = notifications .where((n) => n.deviceId == deviceId.value) .toList(); final List notifList = []; for (var notif in filteredNotifications) { try { final patient = await _firestoreService .getPatientByDeviceId(notif.deviceId); final room = patient != null ? await _firestoreService.getRoomById(patient.roomId) : null; notifList.add( NotificationData( id: notif.id, title: notif.title, message: notif.message, room: room?.roomName ?? '-', deviceId: notif.deviceId, timeAgo: _getLastUpdateText(notif.createdAt), createdAt: notif.createdAt, ), ); } catch (e) { print('Error processing notification ${notif.id}: $e'); } } notifList.sort((a, b) => b.createdAt.compareTo(a.createdAt)); notificationsList.value = notifList.take(2).toList(); isLoadingNotifications.value = false; } catch (e) { print('Error processing notifications: $e'); isLoadingNotifications.value = false; } }, onError: (error) { print('Notifications stream error: $error'); isLoadingNotifications.value = false; AppSnackbar.error('Gagal memuat notifikasi: $error'); }, cancelOnError: false, ); } catch (e) { print('Error loading notifications: $e'); isLoadingNotifications.value = false; AppSnackbar.error('Gagal memuat notifikasi: $e'); } } void updateCarouselIndex(int index) { currentCarouselIndex.value = index; } void showCallDialog(BuildContext context) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( backgroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), title: const Text( 'Hubungi Perawat', style: TextStyle(fontWeight: FontWeight.bold), ), content: const Text( 'Apakah Anda ingin menghubungi perawat melalui WhatsApp?', ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: Text('Batal', style: TextStyle(color: Colors.grey[600])), ), ElevatedButton( onPressed: () async { Navigator.of(context).pop(); await _openWhatsApp(); }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF25D366), foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.phone, size: 18), SizedBox(width: 8), Text('Hubungi'), ], ), ), ], ); }, ); } Future _openWhatsApp() async { try { const String nursePhoneNumber = '6281231901277'; final String message = Uri.encodeComponent( 'Halo, saya ${patientName.value} di ruangan ${currentInfusData.value.room}. ' 'Saya membutuhkan bantuan perawat untuk device ${deviceId.value}.', ); final String whatsappUrl = 'https://wa.me/$nursePhoneNumber?text=$message'; if (await canLaunchUrl(Uri.parse(whatsappUrl))) { await launchUrl( Uri.parse(whatsappUrl), mode: LaunchMode.externalApplication, ); } else { AppSnackbar.error('Tidak dapat membuka WhatsApp'); } } catch (e) { print('Error opening WhatsApp: $e'); AppSnackbar.error('Gagal menghubungi perawat: $e'); } } void showLogoutDialog(BuildContext context) { showDialog( context: context, builder: (BuildContext context) { return AlertDialog( backgroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), title: const Text( 'Konfirmasi Logout', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), ), content: const Text( 'Yakin ingin keluar?', style: TextStyle(fontSize: 16), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), style: TextButton.styleFrom( foregroundColor: Colors.grey[600], padding: const EdgeInsets.symmetric( vertical: 12, horizontal: 20, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: const Text('Batal'), ), ElevatedButton( onPressed: () async { Navigator.of(context).pop(); try { await FirebaseAuth.instance.signOut(); Get.offAllNamed('/login'); } catch (e) { print('Error logging out: $e'); AppSnackbar.error('Gagal logout: $e'); } }, style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( vertical: 12, horizontal: 20, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: const Text( 'Logout', style: TextStyle(fontWeight: FontWeight.bold), ), ), ], ); }, ); } String _getLastUpdateText(DateTime lastUpdate) { try { final difference = DateTime.now().difference(lastUpdate); if (difference.inMinutes < 1) { return 'Just now'; } else if (difference.inMinutes < 60) { return '${difference.inMinutes}m ago'; } else if (difference.inHours < 24) { return '${difference.inHours}h ago'; } else { return '${difference.inDays}d ago'; } } catch (e) { print('Error calculating time difference: $e'); return 'Unknown'; } } Future refreshData() async { try { await _loadPatientData(); } catch (e) { print('Error refreshing data: $e'); AppSnackbar.error('Gagal refresh data: $e'); } } void clearDeviceAlert(String deviceId) { _alertedDevices.remove(deviceId); _notificationService.clearCooldown(deviceId); } void clearAllAlerts() { _alertedDevices.clear(); _notificationService.clearAllCooldowns(); } }