TKK_E32230859/lib/app/modules/home-patient/controllers/home_patient_controller.dart

592 lines
18 KiB
Dart

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 = <ChartDataPoint>[].obs;
final notificationsList = <NotificationData>[].obs;
final isLoading = true.obs;
final isLoadingHistory = true.obs;
final isLoadingNotifications = true.obs;
StreamSubscription? _realtimeSubscription;
StreamSubscription? _notificationsSubscription;
StreamSubscription? _historySubscription;
final Set<String> _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<void> _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<String, dynamic>?;
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<void> _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<void> _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<void> _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<void> 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<HistoryModel> 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<ChartDataPoint> 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<NotificationData> 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<void> _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<void> 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();
}
}