diff --git a/lib/api/api_client.dart b/lib/api/api_client.dart index 3232ea7..44489d6 100644 --- a/lib/api/api_client.dart +++ b/lib/api/api_client.dart @@ -3,9 +3,9 @@ import 'dart:convert'; import 'dart:developer'; import 'dart:io'; import 'package:flutter/foundation.dart'; -import 'package:http/http.dart'; +import 'package:hmg_qline/services/logger_service.dart'; +import 'package:http/http.dart' as http; import 'package:http/io_client.dart'; -import 'package:hmg_qline/constants/app_constants.dart'; import 'package:hmg_qline/utilities/api_exception.dart'; typedef FactoryConstructor = U Function(dynamic); @@ -14,14 +14,18 @@ abstract class ApiClient { Future postJsonForObject(FactoryConstructor factoryConstructor, String url, T jsonObject, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}); - Future postJsonForResponse(String url, T jsonObject, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}); + Future postJsonForResponse(String url, T jsonObject, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}); Future getJsonForObject(FactoryConstructor factoryConstructor, String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}); - Future getJsonForResponse(String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}); + Future getJsonForResponse(String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}); } class ApiClientImp implements ApiClient { + LoggerService loggerService; + + ApiClientImp({required this.loggerService}); + @override Future postJsonForObject(FactoryConstructor factoryConstructor, String url, T jsonObject, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { @@ -45,7 +49,7 @@ class ApiClientImp implements ApiClient { } @override - Future postJsonForResponse(String url, T jsonObject, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { + Future postJsonForResponse(String url, T jsonObject, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { String? requestBody; if (jsonObject != null) { requestBody = jsonEncode(jsonObject); @@ -59,7 +63,7 @@ class ApiClientImp implements ApiClient { return await _postForResponse(url, requestBody, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes); } - Future _postForResponse( + Future _postForResponse( String url, requestBody, { String? token, @@ -81,17 +85,17 @@ class ApiClientImp implements ApiClient { var queryString = Uri(queryParameters: queryParameters).query; url = '$url?$queryString'; } - Response response; + http.Response response; response = await _post(Uri.parse(url), body: requestBody, headers: headers0).timeout(const Duration(seconds: 100)); if (!kReleaseMode) { - logger.d("------URL------"); - logger.i(url); - logger.d("------Payload------"); - logger.i(jsonDecode(requestBody)); - logger.d("------Response------"); - logger.i(jsonDecode(response.body)); + loggerService.logInfo("------URL------"); + loggerService.logInfo(url); + loggerService.logInfo("------Payload------"); + loggerService.logInfo(jsonDecode(requestBody).toString()); + loggerService.logInfo("------Response------"); + loggerService.logInfo(jsonDecode(response.body).toString()); } if (response.statusCode >= 200 && response.statusCode < 500) { var jsonData = jsonDecode(response.body); @@ -120,7 +124,7 @@ class ApiClientImp implements ApiClient { } } on TimeoutException catch (e) { throw APIException(APIException.timeout, arguments: e); - } on ClientException catch (e) { + } on http.ClientException catch (e) { if (retryTimes > 0) { log('will retry after 3 seconds...'); await Future.delayed(const Duration(seconds: 3)); @@ -136,7 +140,7 @@ class ApiClientImp implements ApiClient { bool _certificateCheck(X509Certificate cert, String host, int port) => true; - Future _withClient(Future Function(Client) fn) async { + Future _withClient(Future Function(http.Client) fn) async { var httpClient = HttpClient()..badCertificateCallback = _certificateCheck; var client = IOClient(httpClient); try { @@ -146,7 +150,7 @@ class ApiClientImp implements ApiClient { } } - Future _post(url, {Map? headers, body, Encoding? encoding}) => _withClient((client) => client.post(url, headers: headers, body: body, encoding: encoding)); + Future _post(url, {Map? headers, body, Encoding? encoding}) => _withClient((client) => client.post(url, headers: headers, body: body, encoding: encoding)); @override Future getJsonForObject(FactoryConstructor factoryConstructor, String url, @@ -166,7 +170,7 @@ class ApiClientImp implements ApiClient { } @override - Future getJsonForResponse(String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { + Future getJsonForResponse(String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { if (headers == null) { headers = {'Content-Type': 'application/json'}; } else { @@ -177,7 +181,7 @@ class ApiClientImp implements ApiClient { bool isFirstCall = true; - Future _getForResponse(String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { + Future _getForResponse(String url, {String? token, Map? queryParameters, Map? headers, int retryTimes = 0}) async { try { var headers0 = {}; if (token != null) { @@ -193,13 +197,13 @@ class ApiClientImp implements ApiClient { } if (!kReleaseMode) { - logger.i(url); - logger.i("$queryParameters"); + loggerService.logInfo(url); + loggerService.logInfo("$queryParameters"); } var response = await _get(Uri.parse(url), headers: headers0).timeout(const Duration(seconds: 60)); if (!kReleaseMode) { - logger.i(jsonDecode(response.body)); + loggerService.logInfo(jsonDecode(response.body)); } if (response.statusCode >= 200 && response.statusCode < 500) { var jsonData = jsonDecode(response.body); @@ -229,7 +233,7 @@ class ApiClientImp implements ApiClient { } } on TimeoutException catch (e) { throw APIException(APIException.timeout, arguments: e); - } on ClientException catch (e) { + } on http.ClientException catch (e) { if (retryTimes > 0) { await Future.delayed(const Duration(seconds: 3)); return await _getForResponse(url, token: token, queryParameters: queryParameters, headers: headers, retryTimes: retryTimes - 1); @@ -239,5 +243,5 @@ class ApiClientImp implements ApiClient { } } - Future _get(url, {Map? headers}) => _withClient((client) => client.get(url, headers: headers)); + Future _get(url, {Map? headers}) => _withClient((client) => client.get(url, headers: headers)); } diff --git a/lib/config/dependency_injection.dart b/lib/config/dependency_injection.dart index 06a6407..140dae5 100644 --- a/lib/config/dependency_injection.dart +++ b/lib/config/dependency_injection.dart @@ -10,28 +10,41 @@ 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/connectivity_service.dart'; +import 'package:hmg_qline/services/logger_service.dart'; import 'package:hmg_qline/services/text_to_speech_service.dart'; import 'package:hmg_qline/view_models/queuing_view_model.dart'; import 'package:hmg_qline/view_models/screen_config_view_model.dart'; import 'package:just_audio/just_audio.dart'; +import 'package:logger/logger.dart'; import 'package:shared_preferences/shared_preferences.dart'; final getIt = GetIt.instance; class AppDependencies { static Future addDependencies() async { + final logger = Logger( + printer: PrettyPrinter( + printEmojis: false, + colors: true, + dateTimeFormat: DateTimeFormat.none, + ), + ); + //api client - getIt.registerSingleton(ApiClientImp()); + getIt.registerSingleton(LoggerServiceImp(logger: logger)); + + getIt.registerSingleton(ApiClientImp(loggerService: getIt.get())); //repos - getIt.registerSingleton(SignalrRepoImp()); - getIt.registerSingleton(ScreenDetailsRepoImp(apiClientInstance: getIt.get())); + getIt.registerSingleton(SignalrRepoImp(loggerService: getIt.get())); + getIt.registerSingleton(ScreenDetailsRepoImp(apiClientInstance: getIt.get(), loggerService: getIt.get())); //ThirdPartyServices + getIt.registerSingleton(ConnectivityServiceImp(connectivityInstance: Connectivity())); getIt.registerSingleton(CacheServiceImp(preferencesInstance: await SharedPreferences.getInstance())); getIt.registerSingleton(AudioServiceImp(audioPlayerInstance: AudioPlayer())); - getIt.registerSingleton(TextToSpeechServiceImp(textToSpeechInstance: FlutterTts())); + getIt.registerSingleton(TextToSpeechServiceImp(textToSpeechInstance: FlutterTts(), loggerService: getIt.get())); //ViewModels @@ -40,11 +53,13 @@ class AppDependencies { screenDetailsRepo: getIt.get(), cacheService: getIt.get(), connectivityService: getIt.get(), + loggerService: getIt.get(), ), ); getIt.registerSingleton( QueuingViewModel( + loggerService: getIt.get(), screenDetailsRepo: getIt.get(), cacheService: getIt.get(), textToSpeechService: getIt.get(), diff --git a/lib/constants/app_constants.dart b/lib/constants/app_constants.dart index ad071c4..ccf3c2b 100644 --- a/lib/constants/app_constants.dart +++ b/lib/constants/app_constants.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:logger/logger.dart'; bool useTestIP = false; -Logger logger = Logger(printer: PrettyPrinter(printEmojis: false, colors: true, dateTimeFormat: DateTimeFormat.none)); // app globals @@ -20,7 +19,10 @@ class AppStrings { static String awaitingQueueNumberAr = "في انتظار رقم قائمة الانتظار"; static String counterNo = "Counter Number: "; static String awaitingArrivalAr = "في انتظار وصول المرضى"; - static String configurationIssueContactAdmin = "There is something wrong with configuration. Please contact Admin."; + static String configurationIssueContactAdmin = "There is something wrong with configuration. Please contact IT Support."; + + static String dataLogsFileName = "data_logs.txt"; + static String errorLogsFileName = "error_logs.txt"; } class AppColors { @@ -102,7 +104,7 @@ class AppConstants { static String testIP = '12.4.5.1'; // projectID.QlineType.ScreenType.AnyNumber (1 to 10) static int thresholdForListUI = 3; - static int currentBuildVersion = 6; + static int currentBuildVersion = 7; } class ApiConstants { diff --git a/lib/repositories/screen_details_repo.dart b/lib/repositories/screen_details_repo.dart index b0759cb..689723e 100644 --- a/lib/repositories/screen_details_repo.dart +++ b/lib/repositories/screen_details_repo.dart @@ -6,6 +6,7 @@ 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/services/logger_service.dart'; import 'package:hmg_qline/utilities/enums.dart'; import 'package:hmg_qline/utilities/extensions.dart'; import 'package:hmg_qline/views/view_helpers/info_components.dart'; @@ -17,8 +18,6 @@ abstract class ScreenDetailsRepo { Future createTicketFromKiosk({required int projectId, required int queueId, int patientId = 0}); - // Future getScreenConfigurationsByIP({required String ipAddress}); - Future getWeatherDetailsByCity({required String cityId}); Future getPrayerDetailsByLatLong({required double latitude, required double longitude}); @@ -32,8 +31,9 @@ abstract class ScreenDetailsRepo { class ScreenDetailsRepoImp implements ScreenDetailsRepo { ApiClient apiClientInstance; + LoggerService loggerService; - ScreenDetailsRepoImp({required this.apiClientInstance}); + ScreenDetailsRepoImp({required this.apiClientInstance, required this.loggerService}); @override Future getGlobalScreenConfigurations({required String ipAddress}) async { @@ -49,11 +49,14 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo { ); List globalConfigurationsModel = List.generate(genericModel.data.length, (index) => GlobalConfigurationsModel.fromJson(json: genericModel.data[index])); if (globalConfigurationsModel.isNotEmpty) { + loggerService.logToFile(globalConfigurationsModel.toString(), type: LogTypeEnum.data); + return globalConfigurationsModel.first; } return null; } catch (e) { - logger.e(e.toString()); + loggerService.logError(e.toString()); + loggerService.logToFile(e.toString(), type: LogTypeEnum.error); InfoComponents.showToast(e.toString()); return null; } @@ -78,7 +81,8 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo { ); return genericModel; } catch (e) { - logger.e(e.toString()); + loggerService.logError(e.toString()); + loggerService.logToFile(e.toString(), type: LogTypeEnum.error); InfoComponents.showToast(e.toString()); return null; } @@ -104,33 +108,13 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo { genericRespModel.data = KioskPatientTicket.fromJson(genericRespModel.data); return genericRespModel; } catch (e) { - logger.e(e.toString()); + loggerService.logError(e.toString()); + loggerService.logToFile(e.toString(), type: LogTypeEnum.error); InfoComponents.showToast(e.toString()); return null; } } - // @override - // Future getScreenConfigurationsByIP({required String ipAddress}) async { - // try { - // final body = { - // "ipAddress": ipAddress.toString(), - // }; - // - // GenericRespModel genericRespModel = await apiClientInstance.postJsonForObject( - // (json) => GenericRespModel.fromJson(json), - // ApiConstants.waitingAreaScreenConfigGet, - // body, - // ); - // WidgetsConfigModel widgetsConfigModel = WidgetsConfigModel.fromJson(genericRespModel.data); - // return widgetsConfigModel; - // } catch (e) { - // logger.e(e.toString()); - // InfoComponents.showToast(e.toString()); - // return null; - // } - // } - @override Future getWeatherDetailsByCity({required String cityId}) async { try { @@ -147,7 +131,8 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo { } return null; } catch (e) { - logger.e(e.toString()); + loggerService.logError(e.toString()); + loggerService.logToFile(e.toString(), type: LogTypeEnum.error); InfoComponents.showToast(e.toString()); return null; } @@ -170,7 +155,8 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo { return null; } catch (e) { - logger.e(e.toString()); + loggerService.logError(e.toString()); + loggerService.logToFile(e.toString(), type: LogTypeEnum.error); InfoComponents.showToast(e.toString()); return null; } @@ -192,7 +178,8 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo { } return null; } catch (e) { - logger.e(e.toString()); + loggerService.logError(e.toString()); + loggerService.logToFile(e.toString(), type: LogTypeEnum.error); InfoComponents.showToast(e.toString()); return null; } @@ -213,7 +200,8 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo { ); return genericModel; } catch (e) { - logger.e(e.toString()); + loggerService.logError(e.toString()); + loggerService.logToFile(e.toString(), type: LogTypeEnum.error); InfoComponents.showToast(e.toString()); return null; } @@ -236,7 +224,8 @@ class ScreenDetailsRepoImp implements ScreenDetailsRepo { ); return genericModel; } catch (e) { - logger.e(e.toString()); + loggerService.logError(e.toString()); + loggerService.logToFile(e.toString(), type: LogTypeEnum.error); InfoComponents.showToast(e.toString()); return null; } diff --git a/lib/repositories/signalR_repo.dart b/lib/repositories/signalR_repo.dart index 4a55775..6facecf 100644 --- a/lib/repositories/signalR_repo.dart +++ b/lib/repositories/signalR_repo.dart @@ -1,12 +1,13 @@ import 'dart:developer'; import 'dart:io'; - +import 'package:hmg_qline/services/logger_service.dart'; +import 'package:hmg_qline/utilities/enums.dart'; import 'package:http/io_client.dart'; import 'package:hmg_qline/constants/app_constants.dart'; import 'package:signalr_core/signalr_core.dart'; abstract class SignalrRepo { - Future startHubConnection({ + Future startHubConnection({ required String deviceIp, required Function(List?) onHubTicketCall, required Function(dynamic) onHubConfigCall, @@ -18,9 +19,12 @@ abstract class SignalrRepo { class SignalrRepoImp implements SignalrRepo { HubConnection? connection; + LoggerService loggerService; + + SignalrRepoImp({this.connection, required this.loggerService}); @override - Future startHubConnection({ + Future startHubConnection({ required String deviceIp, required Function(List?) onHubTicketCall, required Function(dynamic) onHubConfigCall, @@ -28,42 +32,48 @@ class SignalrRepoImp implements SignalrRepo { required Function(dynamic) onHubReconnected, required Function(dynamic) onHubDisconnected, }) async { - String hubBaseURL = ApiConstants.baseUrlHub; + try { + String hubBaseURL = ApiConstants.baseUrlHub; + if ((connection != null) && (connection!.state == HubConnectionState.connected || connection!.state == HubConnectionState.connecting)) { + return true; + } + connection = HubConnectionBuilder() + .withUrl( + "$hubBaseURL?IPAddress=$deviceIp", + HttpConnectionOptions( + client: IOClient(HttpClient()..badCertificateCallback = (x, y, z) => true), + logging: (level, message) => log(message), + )) + .withAutomaticReconnect() + .build(); - if ((connection != null) && (connection!.state == HubConnectionState.connected || connection!.state == HubConnectionState.connecting)) { - return; - } - connection = HubConnectionBuilder() - .withUrl( - "$hubBaseURL?IPAddress=$deviceIp", - HttpConnectionOptions( - client: IOClient(HttpClient()..badCertificateCallback = (x, y, z) => true), - logging: (level, message) => log(message), - )) - .withAutomaticReconnect() - .build(); + connection!.serverTimeoutInMilliseconds = 120000; + connection!.onclose((exception) { + log(exception.toString()); + onHubDisconnected(exception); + }); - connection!.serverTimeoutInMilliseconds = 120000; - connection!.onclose((exception) { - log(exception.toString()); - onHubDisconnected(exception); - }); + connection!.onreconnecting((exception) => onHubConnecting(exception)); + connection!.onreconnected((connectionId) { + log(connectionId.toString()); + onHubReconnected(connectionId); + }); - connection!.onreconnecting((exception) => onHubConnecting(exception)); - connection!.onreconnected((connectionId) { - log(connectionId.toString()); - onHubReconnected(connectionId); - }); + connection!.on(ApiConstants.sendQLinePatientCall, (List? message) { + onHubTicketCall(message); + }); - connection!.on(ApiConstants.sendQLinePatientCall, (List? message) { - onHubTicketCall(message); - }); + connection!.on(ApiConstants.sendQLineConfig, (List? message) { + onHubConfigCall(message); + }); - connection!.on(ApiConstants.sendQLineConfig, (List? message) { - onHubConfigCall(message); - }); - - await connection!.start(); + await connection!.start(); + return true; + } catch (e) { + loggerService.logError(e.toString()); + loggerService.logToFile(e.toString(), type: LogTypeEnum.error); + return false; + } } bool getHubConnectionState() { diff --git a/lib/services/logger_service.dart b/lib/services/logger_service.dart new file mode 100644 index 0000000..0e71890 --- /dev/null +++ b/lib/services/logger_service.dart @@ -0,0 +1,74 @@ +import 'dart:io'; +import 'package:hmg_qline/constants/app_constants.dart'; +import 'package:hmg_qline/utilities/enums.dart'; +import 'package:intl/intl.dart'; +import 'package:logger/logger.dart'; +import 'package:path_provider/path_provider.dart'; + +abstract class LoggerService { + Future logToFile(String message, {LogTypeEnum type = LogTypeEnum.data}); + + Future clearLogs(); + + void logError(String message); + + void logInfo(String message); +} + +class LoggerServiceImp implements LoggerService { + Logger logger; + + LoggerServiceImp({required this.logger}); + + @override + Future logToFile(String message, {LogTypeEnum type = LogTypeEnum.data}) async { + try { + final timestamp = DateFormat('yyyy-MM-dd HH:mm:ss a').format(DateTime.now()); + final formattedMessage = "[$timestamp] ${type.name.toUpperCase()}: $message"; + + final dir = await getApplicationDocumentsDirectory(); + final logDir = Directory('${dir.path}/logs'); + if (!await logDir.exists()) { + await logDir.create(recursive: true); + } + + final logFileName = type == LogTypeEnum.error ? AppStrings.errorLogsFileName : AppStrings.dataLogsFileName; + final file = File('${logDir.path}/$logFileName'); + + await file.writeAsString("$formattedMessage\n", mode: FileMode.append); + } catch (e) { + logger.i('Logging failed: $e'); + } + } + + @override + Future clearLogs() async { + try { + final dir = await getApplicationDocumentsDirectory(); + final logDir = Directory('${dir.path}/logs'); + + final filesToClear = [ + File('${logDir.path}/${AppStrings.errorLogsFileName}'), + File('${logDir.path}/${AppStrings.dataLogsFileName}'), + ]; + + for (var file in filesToClear) { + if (await file.exists()) { + await file.writeAsString(''); + } + } + } catch (e) { + logger.e("Failed to clear logs: ${e.toString()}"); + } + } + + @override + void logError(String message) { + logger.e(message); + } + + @override + void logInfo(String message) { + logger.i(message); + } +} diff --git a/lib/services/text_to_speech_service.dart b/lib/services/text_to_speech_service.dart index 2fd1ca1..abe8f3c 100644 --- a/lib/services/text_to_speech_service.dart +++ b/lib/services/text_to_speech_service.dart @@ -3,8 +3,10 @@ import 'package:flutter_tts/flutter_tts.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/services/logger_service.dart'; import 'package:hmg_qline/utilities/enums.dart'; import 'package:hmg_qline/utilities/extensions.dart'; +import 'package:logger/logger.dart'; abstract class TextToSpeechService { Future speechText({ @@ -20,8 +22,9 @@ abstract class TextToSpeechService { class TextToSpeechServiceImp implements TextToSpeechService { FlutterTts textToSpeechInstance; + LoggerService loggerService; - TextToSpeechServiceImp({required this.textToSpeechInstance}); + TextToSpeechServiceImp({required this.textToSpeechInstance, required this.loggerService}); double volume = 1.0; double pitch = 0.6; @@ -179,17 +182,17 @@ class TextToSpeechServiceImp implements TextToSpeechService { textToSpeechInstance.setCompletionHandler(onVoiceCompleted); textToSpeechInstance.setErrorHandler((message) { - logger.e("setErrorHandler: $message\nCompleting the voice for now"); + loggerService.logInfo("setErrorHandler: $message\nCompleting the voice for now"); isSpeechCompleted = true; onVoiceCompleted(); }); textToSpeechInstance.setPauseHandler(() { - logger.e("setPauseHandler"); + loggerService.logInfo("setPauseHandler"); }); textToSpeechInstance.setCancelHandler(() { - logger.e("setCancelHandler"); + loggerService.logInfo("setCancelHandler"); }); } } diff --git a/lib/utilities/enums.dart b/lib/utilities/enums.dart index d425991..254c7d1 100644 --- a/lib/utilities/enums.dart +++ b/lib/utilities/enums.dart @@ -38,3 +38,8 @@ enum KioskScreenStateEnums { ticketNumState, busyState, } + +enum LogTypeEnum { + data, + error, +} diff --git a/lib/view_models/queuing_view_model.dart b/lib/view_models/queuing_view_model.dart index 878eec3..7e69ec2 100644 --- a/lib/view_models/queuing_view_model.dart +++ b/lib/view_models/queuing_view_model.dart @@ -9,6 +9,7 @@ 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/utilities/enums.dart'; import 'package:hmg_qline/view_models/screen_config_view_model.dart'; @@ -19,6 +20,7 @@ class QueuingViewModel extends ChangeNotifier { final CacheService cacheService; final AudioService audioService; final TextToSpeechService textToSpeechService; + final LoggerService loggerService; QueuingViewModel({ required this.screenDetailsRepo, @@ -26,6 +28,7 @@ class QueuingViewModel extends ChangeNotifier { required this.cacheService, required this.audioService, required this.textToSpeechService, + required this.loggerService, }); Future initializeQueueingVM() async { @@ -34,9 +37,9 @@ class QueuingViewModel extends ChangeNotifier { initializeTextToSpeech(); } - Future startHubConnection() async { + Future startHubConnection() async { ScreenConfigViewModel screenConfigViewModel = getIt.get(); - await signalrRepo.startHubConnection( + return await signalrRepo.startHubConnection( deviceIp: screenConfigViewModel.currentScreenIP, onHubTicketCall: onHubTicketCall, onHubConfigCall: onHubConfigCall, @@ -55,6 +58,8 @@ class QueuingViewModel extends ChangeNotifier { } Future onHubConfigCall(var response) async { + loggerService.logToFile(response.toString(), type: LogTypeEnum.data); + if (response != null && response.isNotEmpty) { var data = response.first['data']; GlobalConfigurationsModel globalConfigurationsModel = GlobalConfigurationsModel.fromJson( @@ -70,6 +75,18 @@ class QueuingViewModel extends ChangeNotifier { } } + Future onHubTicketCall(List? response) async { + loggerService.logToFile(response.toString(), type: LogTypeEnum.data); + + log("onHubTicketCall: $response"); + + log("isCallingInProgress: $isCallingInProgress"); + if (response != null && response.isNotEmpty) { + TicketDetailsModel ticketDetailsModel = TicketDetailsModel.fromJson(response.first as Map); + addNewTicket(ticketDetailsModel); + } + } + Future onHubReconnected(var response) async { log("onHubConnected: $response"); ScreenConfigViewModel screenConfigViewModel = getIt.get(); @@ -124,15 +141,6 @@ class QueuingViewModel extends ChangeNotifier { }); } - Future onHubTicketCall(List? response) async { - log("onHubTicketCall: $response"); - log("isCallingInProgress: $isCallingInProgress"); - if (response != null && response.isNotEmpty) { - TicketDetailsModel ticketDetailsModel = TicketDetailsModel.fromJson(response.first as Map); - addNewTicket(ticketDetailsModel); - } - } - int itemAlreadyAvailableAtIndex({required TicketDetailsModel ticketToSearch, required List listToSearchIn}) { int isFoundAt = -1; try { diff --git a/lib/view_models/screen_config_view_model.dart b/lib/view_models/screen_config_view_model.dart index 855f163..b198d02 100644 --- a/lib/view_models/screen_config_view_model.dart +++ b/lib/view_models/screen_config_view_model.dart @@ -14,11 +14,11 @@ import 'package:hmg_qline/models/weathers_widget_model.dart'; import 'package:hmg_qline/repositories/screen_details_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/view_models/queuing_view_model.dart'; import 'package:hmg_qline/views/view_helpers/info_components.dart'; -import 'package:intl/intl.dart'; import 'package:qr_code_scanner_plus/qr_code_scanner_plus.dart'; // import 'package:timezone/browser.dart' as tz; @@ -26,11 +26,13 @@ class ScreenConfigViewModel extends ChangeNotifier { final ScreenDetailsRepo screenDetailsRepo; final CacheService cacheService; final ConnectivityService connectivityService; + final LoggerService loggerService; ScreenConfigViewModel({ required this.screenDetailsRepo, required this.cacheService, required this.connectivityService, + required this.loggerService, }); Future initializeScreenConfigVM() async { @@ -40,12 +42,6 @@ class ScreenConfigViewModel extends ChangeNotifier { getTheWidgetsConfigurationsEveryMidnight(); } - // Future initializeTimezone() async { - // await tz.initializeTimeZone(); - // var detroit = tz.getLocation('America/Detroit'); - // var now = tz.TZDateTime.now(detroit); - // } - Future waitForIPAndInitializeConfigVM() async { while (currentScreenIP == "") { await getCurrentScreenIP(); @@ -123,7 +119,8 @@ class ScreenConfigViewModel extends ChangeNotifier { GlobalConfigurationsModel? response = await screenDetailsRepo.getGlobalScreenConfigurations(ipAddress: currentScreenIP); if (response == null) { - logger.e("response was: $response"); + loggerService.logError("response was: $response"); + updateViewState(ViewState.error); return; } updateGlobalConfigurationsModel(value: response); @@ -216,11 +213,17 @@ class ScreenConfigViewModel extends ChangeNotifier { if (!isHubConnected) { log("Hub is Not Connected!, I will try to reconnect now."); QueuingViewModel queuingViewModel = getIt.get(); - queuingViewModel.startHubConnection(); + bool? status = await queuingViewModel.startHubConnection(); + if (status != false) { + updateIsHubConnected(true); + } } else { log("Hub is Connected!"); } + // log("Data Before Check : ${prayersWidgetModel.fajr}"); + // log("Data Before Check : ${globalConfigurationsModel.isPrayerTimeReq}"); + if (globalConfigurationsModel.isPrayerTimeReq && prayersWidgetModel.fajr == null) { await getPrayerDetailsFromServer(); } @@ -386,13 +389,13 @@ class ScreenConfigViewModel extends ChangeNotifier { patientId: patientId, ); if (response == null || response.messageStatus != 1) { - logger.e("response null from createTicketFromKiosk"); + loggerService.logError("response null from createTicketFromKiosk"); return null; } return response.data; } catch (e) { InfoComponents.showToast(e.toString()); - logger.i(e.toString()); + loggerService.logError(e.toString()); return null; } } @@ -404,10 +407,10 @@ class ScreenConfigViewModel extends ChangeNotifier { qTypeEnum: globalConfigurationsModel.qTypeEnum, ); if (response == null || response.messageStatus != 1) { - logger.e("response null from acknowledgeTicket"); + loggerService.logError("response null from acknowledgeTicket"); return; } else { - logger.i("response from acknowledgeTicket ${response.data}"); + loggerService.logInfo("response from acknowledgeTicket ${response.data}"); } } @@ -418,10 +421,10 @@ class ScreenConfigViewModel extends ChangeNotifier { callTypeEnum: callTypeEnum, ); if (response == null || response.messageStatus != 1) { - logger.e("response null from acknowledgeTicket"); + loggerService.logError("response null from acknowledgeTicketForAppointmentOnly"); return; } else { - logger.i("response from acknowledgeTicket ${response.data}"); + loggerService.logInfo("response from acknowledgeTicketForAppointmentOnly ${response.data}"); } } @@ -463,7 +466,7 @@ class ScreenConfigViewModel extends ChangeNotifier { qrViewController = controller; controller.scannedDataStream.listen((scanData) async { - logger.i("Found: ${scanData.code} and isGeneratingTicket: $isGeneratingTicket"); + loggerService.logInfo("Found: ${scanData.code} and isGeneratingTicket: $isGeneratingTicket"); if (isGeneratingTicket) return; await validateAndGenerateTicketFromQR(data: scanData, onFailure: onFailure, onSuccess: onSuccess); }); diff --git a/lib/views/common_widgets/app_general_widgets.dart b/lib/views/common_widgets/app_general_widgets.dart index 5621dac..48a4537 100644 --- a/lib/views/common_widgets/app_general_widgets.dart +++ b/lib/views/common_widgets/app_general_widgets.dart @@ -133,13 +133,21 @@ Widget counterNoText({required int counterNo, required bool isRoomNoRequired, re ); } -Widget noPatientInQueue({required String text, required String fontName, required String roomText, required bool isRoomNoRequired, required bool isForRoomLevel, required int counterNo}) { +Widget intimationWidget({ + required String text, + required String fontName, + required String roomText, + required bool isRoomNoRequired, + required bool isForRoomLevel, + required int counterNo, + bool isForError = false, +}) { Widget noPatientText = Center( child: AppText( text, fontFamily: fontName, textAlign: TextAlign.center, - fontSize: SizeConfig.getWidthMultiplier() * 9, + fontSize: isForError ? SizeConfig.getWidthMultiplier() * 7 : SizeConfig.getWidthMultiplier() * 9, ), ); if (isForRoomLevel) { diff --git a/lib/views/kiosk_screens/kiosk_main_screen.dart b/lib/views/kiosk_screens/kiosk_main_screen.dart index 39ef44d..8ad612a 100644 --- a/lib/views/kiosk_screens/kiosk_main_screen.dart +++ b/lib/views/kiosk_screens/kiosk_main_screen.dart @@ -59,11 +59,12 @@ class KioskMainScreen extends StatelessWidget { bool isLanguageConfigAvailable = screenConfigViewModel.globalConfigurationsModel.kioskLanguageConfigList != null; if (!isLanguageConfigAvailable) { - return noPatientInQueue( + return intimationWidget( text: AppStrings.configurationIssueContactAdmin, fontName: AppStrings.fontNamePoppins, isForRoomLevel: false, isRoomNoRequired: false, + isForError: true, counterNo: 0, roomText: '', ); @@ -105,11 +106,12 @@ class KioskMainScreen extends StatelessWidget { Widget configurationWidgetIssue() { return Center( - child: noPatientInQueue( + child: intimationWidget( text: AppStrings.configurationIssueContactAdmin, fontName: AppStrings.fontNamePoppins, isForRoomLevel: false, isRoomNoRequired: false, + isForError: true, counterNo: 0, roomText: '', ), diff --git a/lib/views/main_queue_screen/main_queue_screen.dart b/lib/views/main_queue_screen/main_queue_screen.dart index a7918de..7fcf7d5 100644 --- a/lib/views/main_queue_screen/main_queue_screen.dart +++ b/lib/views/main_queue_screen/main_queue_screen.dart @@ -40,9 +40,20 @@ class MainQueueScreen extends StatelessWidget { text = AppStrings.awaitingArrivalAr; fontFamily = AppStrings.fontNameCairo; } - if (queuingViewModel.currentTickets.isEmpty) { + + if (screenConfigViewModel.state == ViewState.error) { + widget = intimationWidget( + text: AppStrings.configurationIssueContactAdmin, + fontName: AppStrings.fontNamePoppins, + isForRoomLevel: false, + isRoomNoRequired: false, + isForError: true, + counterNo: 0, + roomText: '', + ); + } else if (queuingViewModel.currentTickets.isEmpty) { bool isForRoomLevel = screenConfigViewModel.globalConfigurationsModel.screenTypeEnum == ScreenTypeEnum.roomLevelScreen; - widget = noPatientInQueue( + widget = intimationWidget( text: text, fontName: fontFamily, isForRoomLevel: isForRoomLevel, diff --git a/pubspec.lock b/pubspec.lock index 4f03401..d3f7080 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -385,7 +385,7 @@ packages: source: hosted version: "1.1.0" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" diff --git a/pubspec.yaml b/pubspec.yaml index f2d0433..a0a31ad 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -52,6 +52,7 @@ dependencies: logger: ^2.4.0 fluttertoast: ^8.2.8 qr_code_scanner_plus: ^2.0.10+1 + path_provider: ^2.1.5 # esc_pos_printer: ^4.0.0 # Ensure you are using the latest version # esc_pos_utils: ^1.0.0