import 'dart:developer'; import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:hmg_qline/config/dependency_injection.dart'; import 'package:hmg_qline/constants/app_constants.dart'; import 'package:hmg_qline/main.dart'; import 'package:hmg_qline/models/generic_response_model.dart'; import 'package:hmg_qline/models/global_config_model.dart'; import 'package:hmg_qline/models/kiosk_language_config_model.dart'; import 'package:hmg_qline/models/kiosk_queue_model.dart'; import 'package:hmg_qline/models/kiosk_ticket_model.dart'; import 'package:hmg_qline/models/prayers_widget_model.dart'; import 'package:hmg_qline/models/rss_feed_model.dart'; import 'package:hmg_qline/models/weathers_widget_model.dart'; import 'package:hmg_qline/repositories/screen_details_repo.dart'; import 'package:hmg_qline/repositories/signalR_repo.dart'; import 'package:hmg_qline/services/cache_service.dart'; import 'package:hmg_qline/services/connectivity_service.dart'; import 'package:hmg_qline/services/logger_service.dart'; import 'package:hmg_qline/utilities/enums.dart'; import 'package:hmg_qline/utilities/extensions.dart'; import 'package:hmg_qline/utilities/native_method_handler.dart'; import 'package:hmg_qline/view_models/queuing_view_model.dart'; import 'package:hmg_qline/views/view_helpers/info_components.dart'; import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart'; // import 'package:timezone/browser.dart' as tz; class ScreenConfigViewModel extends ChangeNotifier { final ScreenDetailsRepo screenDetailsRepo; final CacheService cacheService; final ConnectivityService connectivityService; final LoggerService loggerService; final NativeMethodChannelService nativeMethodChannelService; ScreenConfigViewModel({ required this.screenDetailsRepo, required this.cacheService, required this.connectivityService, required this.loggerService, required this.nativeMethodChannelService, }); Future initializeScreenConfigVM() async { await getGlobalConfigurationsByIP(); await getLastTimeUpdatedFromCache(); await getLastTimeLogsClearedFromCache(); listenNetworkConnectivity(); getTheWidgetsConfigurationsEveryMidnight(); } Future onAppResumed() async { loggerService.logToFile(message: "[didChangeAppLifecycleState] : [onAppResumed]", source: "onAppResumed -> screen_config_view_model.dart", type: LogTypeEnum.data); } Future onAppPaused() async { loggerService.logToFile(message: "[didChangeAppLifecycleState] : [onAppPaused]", source: "onAppPaused -> screen_config_view_model.dart", type: LogTypeEnum.data); // nativeMethodChannelService.reopenApp(); nativeMethodChannelService.restartApp(); // runApp(const MyApp()); } Future waitForIPAndInitializeConfigVM() async { while (currentScreenIP == "") { await getCurrentScreenIP(); if (currentScreenIP != "") { initializeScreenConfigVM(); } else { await Future.delayed(const Duration(seconds: 2)); } } } bool isInternetConnected = true; updateIsInternetConnected(bool value) { isInternetConnected = value; notifyListeners(); } bool isHubConnected = true; updateIsHubConnected(bool value) { isHubConnected = value; notifyListeners(); } ViewState state = ViewState.idle; void updateViewState(ViewState viewState) { state = viewState; notifyListeners(); } updateCurrentScreenRotation(ScreenOrientationEnum value) { globalConfigurationsModel.orientationTypeEnum = value; notifyListeners(); } ScreenTypeEnum currentScreenTypeEnum = ScreenTypeEnum.waitingAreaScreen; updateCurrentScreenTypeEnum(ScreenTypeEnum value) { currentScreenTypeEnum = value; } QTypeEnum currentQTypeEnum = QTypeEnum.lab; updateCurrentQTypeEnum(QTypeEnum value) { currentQTypeEnum = value; } String currentScreenIP = ""; Future getCurrentScreenIP() async { if (useTestIP) { currentScreenIP = AppConstants.testIP; } else { currentScreenIP = await connectivityService.getCurrentScreenIP(); log("currentScreenIP: $currentScreenIP"); } } void listenNetworkConnectivity() { return connectivityService.subscribeToConnectivityChange( onInternetDisConnected: () { updateIsInternetConnected(false); updateIsHubConnected(false); }, onInternetConnected: () { updateIsInternetConnected(true); QueuingViewModel queuingViewModel = getIt.get(); queuingViewModel.startHubConnection(); }, ); } GlobalConfigurationsModel globalConfigurationsModel = GlobalConfigurationsModel(); Future getGlobalConfigurationsByIP() async { GlobalConfigurationsModel? response = await screenDetailsRepo.getGlobalScreenConfigurations(ipAddress: currentScreenIP); if (response == null) { loggerService.logError("response was: $response"); updateViewState(ViewState.error); return; } updateGlobalConfigurationsModel(value: response); updateCurrentScreenTypeEnum(globalConfigurationsModel.screenTypeEnum); updateCurrentQTypeEnum(globalConfigurationsModel.qTypeEnum); notifyListeners(); } void updateGlobalConfigurationsModel({required var value, bool needNotify = false, bool shouldUpdateNextPrayer = false}) { globalConfigurationsModel = value; if (needNotify) { notifyListeners(); } if (shouldUpdateNextPrayer) { getNextPrayerToShow(); } getInfoWidgetsDetailsFromServer(); } Future getInfoWidgetsDetailsFromServer() async { if (globalConfigurationsModel.isWeatherReq) { await getWeatherDetailsFromServer(); } if (globalConfigurationsModel.isPrayerTimeReq) { await getPrayerDetailsFromServer(); } if (globalConfigurationsModel.isRssFeedReq) { await getRssFeedDetailsFromServer(); } int currentDate = DateTime.now().millisecondsSinceEpoch; await cacheService.setLastTimeUpdatedInCache(lasTimeUpdated: currentDate.toString()); } RssFeedModel rssFeedModel = RssFeedModel(); Future getRssFeedDetailsFromServer() async { RssFeedModel? response = await screenDetailsRepo.getRssFeedDetailsByLanguageID(languageId: 0); if (response == null) { return; } rssFeedModel = response; notifyListeners(); } PrayersWidgetModel prayersWidgetModel = PrayersWidgetModel(); Future getPrayerDetailsFromServer() async { double testLatitude = 24.722136; double testLongitude = 46.774303; PrayersWidgetModel? response = await screenDetailsRepo.getPrayerDetailsByLatLong( latitude: globalConfigurationsModel.projectLatitude == 0.0 ? testLatitude : globalConfigurationsModel.projectLatitude ?? testLatitude, longitude: globalConfigurationsModel.projectLongitude == 0.0 ? testLongitude : globalConfigurationsModel.projectLongitude ?? testLongitude, ); if (response == null) { return; } prayersWidgetModel = response; getNextPrayerToShow(); notifyListeners(); } WeathersWidgetModel weathersWidgetModel = WeathersWidgetModel(); Future getWeatherDetailsFromServer() async { int testCityKey = 297030; WeathersWidgetModel? response = await screenDetailsRepo.getWeatherDetailsByCity( cityId: ((globalConfigurationsModel.cityKey == null || globalConfigurationsModel.cityKey == 0) ? testCityKey : globalConfigurationsModel.cityKey).toString(), ); if (response == null) { return; } weathersWidgetModel = response; notifyListeners(); } String nextPrayerToShowWithTime = ''; void getNextPrayerToShow() async { final current = DateTime.now(); log("Checking Namaz time Locally at $current and ${current.timeZoneName}"); // log("Data Before Check : ${prayersWidgetModel.fajr}"); // log("Data Before Check : ${globalConfigurationsModel.isPrayerTimeReq}"); if (globalConfigurationsModel.isPrayerTimeReq && prayersWidgetModel.fajr == null) { await getPrayerDetailsFromServer(); } // log("prayersWidgetModel.dhuhr: ${prayersWidgetModel.dhuhr!}"); // log("dhuhr: ${prayersWidgetModel.dhuhr!.toDateTimeFromInt()}"); // log("current: ${DateFormat('yyyy-MM-dd hh:mm a').format(DateTime.now().toLocal())}"); // log("current: ${DateTime.now().timeZoneName}"); if (prayersWidgetModel.fajr != null && prayersWidgetModel.fajr!.toDateTimeFromInt().isAfter(current)) { final namazTime = prayersWidgetModel.fajr!.toFormattedDateTimeFromInt(); nextPrayerToShowWithTime = "${globalConfigurationsModel.fajarText} at $namazTime"; notifyListeners(); return; } if (prayersWidgetModel.dhuhr != null && prayersWidgetModel.dhuhr!.toDateTimeFromInt().isAfter(current)) { final namazTime = prayersWidgetModel.dhuhr!.toFormattedDateTimeFromInt(); nextPrayerToShowWithTime = "${globalConfigurationsModel.dhuhrText} at $namazTime"; notifyListeners(); return; } if (prayersWidgetModel.asr != null && prayersWidgetModel.asr!.toDateTimeFromInt().isAfter(current)) { final namazTime = prayersWidgetModel.asr!.toFormattedDateTimeFromInt(); nextPrayerToShowWithTime = "${globalConfigurationsModel.asarText} at $namazTime"; notifyListeners(); return; } if (prayersWidgetModel.maghrib != null && prayersWidgetModel.maghrib!.toDateTimeFromInt().isAfter(current)) { final namazTime = prayersWidgetModel.maghrib!.toFormattedDateTimeFromInt(); nextPrayerToShowWithTime = "${globalConfigurationsModel.maghribText} at $namazTime"; notifyListeners(); return; } if (prayersWidgetModel.isha != null && prayersWidgetModel.isha!.toDateTimeFromInt().isAfter(current)) { final namazTime = prayersWidgetModel.isha!.toFormattedDateTimeFromInt(); nextPrayerToShowWithTime = "${globalConfigurationsModel.ishaText} at $namazTime"; notifyListeners(); return; } final namazTime = prayersWidgetModel.fajr!.toFormattedDateTimeFromInt(); nextPrayerToShowWithTime = "${globalConfigurationsModel.fajarText} at $namazTime"; notifyListeners(); return; } int counter = 0; DateTime lastChecked = DateTime.now(); Timer? _midnightTimer; Future getTheWidgetsConfigurationsEveryMidnight() async { // Cancel any existing timer to avoid multiple timers running _midnightTimer?.cancel(); if (!(globalConfigurationsModel.isWeatherReq) && !(globalConfigurationsModel.isPrayerTimeReq) && !(globalConfigurationsModel.isRssFeedReq)) { return; } // Only start timer if not already running if (_midnightTimer != null) { return; } _midnightTimer = Timer.periodic(const Duration(minutes: 1), (timer) async { counter++; DateTime now = DateTime.now(); log("counterValue: $counter"); // Every hour, update RSS feed if required if (counter % 60 == 0 && globalConfigurationsModel.isRssFeedReq) { await getRssFeedDetailsFromServer(); } log("lastChecked: [${lastChecked.day}]"); log("now: [${now.day}]"); // At midnight, update weather and prayer details if required if (now.day != lastChecked.day) { if (now.difference(now.copyWith(hour: 0, minute: 0, second: 0, millisecond: 0, microsecond: 0)).inMinutes >= 5) { if (globalConfigurationsModel.isWeatherReq) { await getWeatherDetailsFromServer(); } if (globalConfigurationsModel.isPrayerTimeReq) { await getPrayerDetailsFromServer(); } lastChecked = now; } } getNextPrayerToShow(); syncHubConnectionState(); }); } @override void dispose() { _midnightTimer?.cancel(); patientIdController.dispose(); super.dispose(); } DateTime currentLastTimeUpdated = DateTime.now(); Future getLastTimeUpdatedFromCache() async { DateTime? response = await cacheService.getLastTimeUpdatedFromCache(); if (response != null) { currentLastTimeUpdated = response; } } DateTime? lastTimeLogsCleared = DateTime.now(); Future getLastTimeLogsClearedFromCache() async { lastTimeLogsCleared = await cacheService.getLastTimeLogsCleared(); if (lastTimeLogsCleared == null) { await cacheService.setLastTimeLogsCleared(lastTimeCleared: DateTime.now().millisecondsSinceEpoch).whenComplete(() => lastTimeLogsCleared = DateTime.now()); } } // *************************** KIOSK FUNCTIONS ************************* KioskScreenStateEnums kioskScreenStateEnum = KioskScreenStateEnums.languageState; void updateKioskScreenState(KioskScreenStateEnums state) { kioskScreenStateEnum = state; notifyListeners(); } KioskPatientTicket? kioskPatientTicket = KioskPatientTicket(); void updateTicketGeneratedFromKiosk(KioskPatientTicket? value) { kioskPatientTicket = value; notifyListeners(); } KioskLanguageConfigModel? currentSelectedKioskLanguage; void updateCurrentSelectedKioskLanguageModel(KioskLanguageConfigModel value) { currentSelectedKioskLanguage = value; notifyListeners(); } late KioskQueueModel currentSelectedKioskQueueModel; void updateCurrentSelectedKioskQueueModel(KioskQueueModel value) { currentSelectedKioskQueueModel = value; notifyListeners(); } Future generateTicketForQueue({required KioskQueueModel kioskQueueModel, int patientId = 0}) async { updateKioskScreenState(KioskScreenStateEnums.busyState); KioskPatientTicket? kioskPatientTicket = await createTicketFromKiosk( projectId: kioskQueueModel.projectID ?? 0, queueId: kioskQueueModel.queueID ?? 0, patientId: patientId, ); if (kioskPatientTicket == null) { updateKioskScreenState(KioskScreenStateEnums.languageState); return false; } updateTicketGeneratedFromKiosk(kioskPatientTicket); updateKioskScreenState(KioskScreenStateEnums.ticketNumState); return true; } Future createTestTickets({required int numOfTicketsToCreate}) async { int startTicket = 123457100; for (int i = 0; i < numOfTicketsToCreate; i++) { GenericRespModel? response = await screenDetailsRepo.createTestTickets(ticketNumber: startTicket); startTicket = startTicket + 1; if (response == null || response.messageStatus != 1) { log("response null from createNextTickets"); return; } } log("last ticket is: $startTicket "); } Future createTicketFromKiosk({required int projectId, required int queueId, int patientId = 0}) async { try { GenericRespModel? response = await screenDetailsRepo.createTicketFromKiosk( projectId: projectId, queueId: queueId, patientId: patientId, ); if (response == null || response.messageStatus != 1) { loggerService.logError("response null from createTicketFromKiosk"); return null; } return response.data; } catch (e) { InfoComponents.showToast(e.toString()); loggerService.logError(e.toString()); return null; } } Future acknowledgeTicket({required int ticketQueueID}) async { GenericRespModel? response = await screenDetailsRepo.acknowledgeTicket( ipAddress: currentScreenIP, ticketQueueID: ticketQueueID, qTypeEnum: globalConfigurationsModel.qTypeEnum, ); if (response == null || response.messageStatus != 1) { loggerService.logError("response null from acknowledgeTicket"); return; } else { loggerService.logInfo("response from acknowledgeTicket ${response.data}"); } } Future acknowledgeTicketForAppointmentOnly({required int ticketQueueID, required String ipAddress, required CallTypeEnum callTypeEnum}) async { GenericRespModel? response = await screenDetailsRepo.acknowledgeTicketForAppointment( ticketId: ticketQueueID, ipAddress: ipAddress, callTypeEnum: callTypeEnum, ); if (response == null || response.messageStatus != 1) { loggerService.logError("response null from acknowledgeTicketForAppointmentOnly"); return; } else { loggerService.logInfo("response from acknowledgeTicketForAppointmentOnly ${response.data}"); } } // ***************************** TEXT INPUT PATIENT ID FUNCTIONS ********************* final TextEditingController patientIdController = TextEditingController(); Future onPatientIdSubmitted(String text) async { int? patientId = int.tryParse(text); if (patientId != null && patientId > 0) { isGeneratingTicket = true; await generateTicketForQueue(kioskQueueModel: currentSelectedKioskQueueModel, patientId: patientId); patientIdController.clear(); await Future.delayed(const Duration(seconds: 2)).whenComplete(() => isGeneratingTicket = false); } } // *************************** QR SCANNER FUNCTIONS ************************* Barcode? qrCodeResult; QRViewController? qrViewController; bool isGeneratingTicket = false; Future flipCamera() async { await qrViewController!.flipCamera(); } void onQRViewCreated({ required QRViewController controller, required VoidCallback onSuccess, required VoidCallback onFailure, }) async { qrViewController = controller; controller.scannedDataStream.listen((scanData) async { loggerService.logInfo("Found: ${scanData.code} and isGeneratingTicket: $isGeneratingTicket"); if (isGeneratingTicket) return; await validateAndGenerateTicketFromQR(data: scanData, onFailure: onFailure, onSuccess: onSuccess); }); await flipCamera(); } Future validateAndGenerateTicketFromQR({required var data, required VoidCallback onSuccess, required VoidCallback onFailure}) async { if (data == null) return; qrCodeResult = data; notifyListeners(); final code = qrCodeResult!.code; int? patientId = int.tryParse(code ?? ''); if (patientId != null && patientId > 0) { isGeneratingTicket = true; bool status = await generateTicketForQueue(kioskQueueModel: currentSelectedKioskQueueModel, patientId: patientId); qrCodeResult = null; notifyListeners(); if (status) { log("status: $status"); onSuccess(); } await Future.delayed(const Duration(seconds: 2)).whenComplete(() => isGeneratingTicket = false); } } Future reassemble() async { if (qrViewController != null) { if (defaultTargetPlatform == TargetPlatform.android) { await qrViewController!.pauseCamera(); } else if (defaultTargetPlatform == TargetPlatform.iOS) { await qrViewController!.resumeCamera(); } await qrViewController!.flipCamera(); } } void syncHubConnectionState() async { QueuingViewModel queuingViewModel = getIt.get(); bool newState = (queuingViewModel.signalrRepo as SignalrRepoImp).isConnected; if (isHubConnected != newState) { updateIsHubConnected(newState); } if (!isHubConnected) { log("Hub is Not Connected!, I will try to reconnect now."); QueuingViewModel queuingViewModel = getIt.get(); bool? status = await queuingViewModel.startHubConnection(); // syncHubConnectionState will update isHubConnected } else { log("Hub is Connected!"); } } }