You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
HMG_QLine/lib/view_models/queuing_view_model.dart

251 lines
10 KiB
Dart

import 'dart:async';
import 'dart:developer';
import 'package:flutter/cupertino.dart';
import 'package:hmg_qline/config/dependency_injection.dart';
import 'package:hmg_qline/constants/app_constants.dart';
import 'package:hmg_qline/models/global_config_model.dart';
import 'package:hmg_qline/models/ticket_model.dart';
import 'package:hmg_qline/repositories/screen_details_repo.dart';
import 'package:hmg_qline/repositories/signalR_repo.dart';
import 'package:hmg_qline/services/audio_service.dart';
import 'package:hmg_qline/services/cache_service.dart';
import 'package:hmg_qline/services/logger_service.dart';
import 'package:hmg_qline/services/text_to_speech_service.dart';
import 'package:hmg_qline/services/crash_handler_service.dart';
import 'package:hmg_qline/utilities/enums.dart';
import 'package:hmg_qline/view_models/screen_config_view_model.dart';
class QueuingViewModel extends ChangeNotifier {
final ScreenDetailsRepo screenDetailsRepo;
final SignalrRepo signalrRepo;
final CacheService cacheService;
final AudioService audioService;
final TextToSpeechService textToSpeechService;
final LoggerService loggerService;
QueuingViewModel({
required this.screenDetailsRepo,
required this.signalrRepo,
required this.cacheService,
required this.audioService,
required this.textToSpeechService,
required this.loggerService,
});
Future<void> initializeQueueingVM() async {
try {
await startHubConnection();
initializeAudioPlayer();
initializeTextToSpeech();
} catch (e, st) {
await getIt.get<CrashHandlerService>().handleCrash(error: e, stackTrace: st, context: 'initializeQueueingVM -> queuing_view_model.dart');
}
}
Future<bool?> startHubConnection() async {
try {
ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>();
return await signalrRepo.startHubConnection(
deviceIp: screenConfigViewModel.currentScreenIP,
onHubTicketCall: onHubTicketCall,
onHubConfigCall: onHubConfigCall,
onHubReconnected: onHubReconnected,
onHubDisconnected: onHubDisconnected,
onHubConnecting: onHubConnecting,
);
} catch (e, st) {
await getIt.get<CrashHandlerService>().handleCrash(error: e, stackTrace: st, context: 'startHubConnection -> queuing_view_model.dart');
return false;
}
}
Future<void> stopHubConnection() async {
return await signalrRepo.closeConnection();
}
initializeAudioPlayer() {
audioService.listenAudioPlayerEvents(onToneCompleted: onToneCompleted);
}
initializeTextToSpeech() {
textToSpeechService.listenToTextToSpeechEvents(onVoiceCompleted: onVoiceCompleted);
}
Future<void> onHubConfigCall(var response) async {
loggerService.logToFile(message: response.toString(), source: "onHubConfigCall -> queueing_view_model.dart ", type: LogTypeEnum.data);
if (response != null && response.isNotEmpty) {
var data = response.first['data'];
GlobalConfigurationsModel globalConfigurationsModel = GlobalConfigurationsModel.fromJson(
json: data,
qType: response.first['qType'] ?? 1,
screenType: response.first['screenType'] ?? 1,
);
log("onHubConfigCall: ${globalConfigurationsModel.toString()}");
ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>();
screenConfigViewModel.updateGlobalConfigurationsModel(value: globalConfigurationsModel, needNotify: true, shouldUpdateNextPrayer: true);
}
}
Future<void> onHubTicketCall(List<Object?>? response) async {
try {
loggerService.logToFile(message: response.toString(), source: "onHubTicketCall -> queueing_view_model.dart ", type: LogTypeEnum.data);
log("onHubTicketCall: $response");
log("isCallingInProgress: $isCallingInProgress");
if (response != null && response.isNotEmpty) {
TicketDetailsModel ticketDetailsModel = TicketDetailsModel.fromJson(response.first as Map<String, dynamic>);
addNewTicket(ticketDetailsModel);
}
} catch (e, st) {
await getIt.get<CrashHandlerService>().handleCrash(error: e, stackTrace: st, context: 'onHubTicketCall -> queuing_view_model.dart');
}
}
Future<void> onHubReconnected(var response) async {
log("onHubConnected: $response");
loggerService.logToFile(message: response.toString(), source: "onHubReconnected -> queueing_view_model.dart ", type: LogTypeEnum.connectivity);
ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>();
screenConfigViewModel.updateIsHubConnected(true);
screenConfigViewModel.notifyListeners();
}
Future<void> onHubDisconnected(var response) async {
log("onHubDisconnected: $response");
loggerService.logToFile(message: response.toString(), source: "onHubDisconnected -> queueing_view_model.dart ", type: LogTypeEnum.connectivity);
ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>();
screenConfigViewModel.updateIsHubConnected(false);
screenConfigViewModel.notifyListeners();
}
Future<void> onHubConnecting(var response) async => log("onHubConnecting: $response");
Future<void> onToneCompleted() async {
GlobalConfigurationsModel globalConfigurationsModel = getIt.get<ScreenConfigViewModel>().globalConfigurationsModel;
await textToSpeechService.speechText(globalConfigurationsModel: globalConfigurationsModel, ticket: currentTickets.first, isMute: !(globalConfigurationsModel.isVoiceReq));
}
Future<void> onVoiceCompleted() async {
log("isSpeechCompleted::: $isSpeechCompleted");
if (isSpeechCompleted) {
waitAndCallNextTicketIfAvailable();
}
}
waitAndCallNextTicketIfAvailable() {
GlobalConfigurationsModel globalConfigurationsModel = getIt.get<ScreenConfigViewModel>().globalConfigurationsModel;
Timer(Duration(seconds: globalConfigurationsModel.concurrentCallDelaySec), () async {
if (queueTickets.isNotEmpty) {
int index = itemAlreadyAvailableAtIndex(ticketToSearch: queueTickets.first, listToSearchIn: currentTickets);
if (index != -1) {
currentTickets.removeAt(index);
}
currentTickets.insert(0, queueTickets.first);
if (currentTickets.length > globalConfigurationsModel.screenMaxDisplayPatients) {
currentTickets.removeLast();
}
notifyListeners();
queueTickets.removeAt(0);
callTicketOnScreen(ticketData: currentTickets.first.ticketModel);
} else {
isCallingInProgress = false;
}
});
}
int itemAlreadyAvailableAtIndex({required TicketDetailsModel ticketToSearch, required List<TicketDetailsModel> listToSearchIn}) {
int isFoundAt = -1;
try {
isFoundAt = listToSearchIn.indexWhere((ticket) {
log("ticket.ticketModel!.queueNo: ${ticket.ticketModel!.queueNo}");
log("ticketToSearch.ticketModel!.queueNo: ${ticketToSearch.ticketModel!.queueNo}");
return ticket.ticketModel!.queueNo == ticketToSearch.ticketModel!.queueNo;
});
log("itemAlreadyAvailableAtIndex: $isFoundAt");
return isFoundAt;
} catch (e) {
log("Error isItemAlreadyAvailableInList: ${e.toString()}");
return isFoundAt;
}
}
bool isCallingInProgress = false;
// Tickets Management
List<TicketDetailsModel> currentTickets = [];
List<TicketDetailsModel> queueTickets = [];
void addNewTicket(TicketDetailsModel ticket) {
if (!isCallingInProgress) {
int index = itemAlreadyAvailableAtIndex(ticketToSearch: ticket, listToSearchIn: currentTickets);
if (index != -1) {
currentTickets.removeAt(index);
}
currentTickets.insert(0, ticket);
GlobalConfigurationsModel globalConfigurationsModel = getIt.get<ScreenConfigViewModel>().globalConfigurationsModel;
if (currentTickets.length > globalConfigurationsModel.screenMaxDisplayPatients) {
currentTickets.removeLast();
}
callTicketOnScreen(ticketData: currentTickets.first.ticketModel);
} else {
int index = itemAlreadyAvailableAtIndex(ticketToSearch: ticket, listToSearchIn: queueTickets);
if (index != -1) {
queueTickets.removeAt(index);
}
queueTickets.add(ticket);
log("inQueue Length: ${queueTickets.length}");
}
notifyListeners();
}
Future<void> callTicketOnScreen({required TicketData? ticketData}) async {
if (ticketData == null) return;
ScreenConfigViewModel screenConfigViewModel = getIt.get<ScreenConfigViewModel>();
GlobalConfigurationsModel globalConfigurationsModel = screenConfigViewModel.globalConfigurationsModel;
if (globalConfigurationsModel.qTypeEnum == QTypeEnum.appointment) {
screenConfigViewModel.acknowledgeTicketForAppointmentOnly(
ticketQueueID: ticketData.id ?? 0,
ipAddress: screenConfigViewModel.currentScreenIP,
callTypeEnum: ticketData.callTypeEnum,
);
} else {
screenConfigViewModel.acknowledgeTicket(ticketQueueID: ticketData.id ?? 0,
ipAddress: screenConfigViewModel.currentScreenIP,
);
}
log("globalConfigurationsModel: ${globalConfigurationsModel.toString()}");
isCallingInProgress = true;
await audioService.playTone(path: AppAssets.callTone, isMute: !(globalConfigurationsModel.isToneReq));
}
List<List<int>> memoryHog = [];
void triggerOOM() {
while (true) {
// Repeatedly add large chunks (e.g., 10 million integers) to a list
// This prevents the Garbage Collector (GC) from reclaiming the memory
memoryHog.add(List.filled(10000000, 9));
}
}
/// Throws an exception from an async context (await) to test runZonedGuarded handling.
Future<void> triggerAsyncCrash() async {
await Future.delayed(const Duration(milliseconds: 50));
throw Exception('Test asynchronous crash from QueuingViewModel.triggerAsyncCrash');
}
/// Creates an unawaited Future that throws to simulate an unhandled async error.
void triggerUnhandledFutureCrash() {
Future.delayed(const Duration(milliseconds: 50), () {
throw Exception('Test unhandled future crash from QueuingViewModel.triggerUnhandledFutureCrash');
});
}
}