diff --git a/assets/images/svg/call_ambulance_icon.svg b/assets/images/svg/call_ambulance_icon.svg new file mode 100644 index 0000000..de3bbc2 --- /dev/null +++ b/assets/images/svg/call_ambulance_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/svg/nearest_er_icon.svg b/assets/images/svg/nearest_er_icon.svg new file mode 100644 index 0000000..73637da --- /dev/null +++ b/assets/images/svg/nearest_er_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/rrt_icon.svg b/assets/images/svg/rrt_icon.svg new file mode 100644 index 0000000..0530487 --- /dev/null +++ b/assets/images/svg/rrt_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 2cbe960..05ed866 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -174,7 +174,7 @@ class ApiClientImp implements ApiClient { } // body['TokenID'] = "@dm!n"; - // body['PatientID'] = 4772172; + // body['PatientID'] = 3966014; // body['PatientTypeID'] = 1; // // body['PatientOutSA'] = 0; diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 9591cfe..787fdad 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -724,6 +724,8 @@ const DEACTIVATE_ACCOUNT = 'Services/Patients.svc/REST/PatientAppleActivation_In const FAMILY_FILES= 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatus'; +var GET_PRESCRIPTION_INSTRUCTIONS_PDF = 'Services/ChatBot_Service.svc/REST/Chatbot_SendMedicationInstructionByWhatsApp'; + class ApiConsts { static const maxSmallScreen = 660; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 1e9eaa4..b675e60 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -155,6 +155,9 @@ class AppAssets { static const String switch_user = '$svgBasePath/switch_user.svg'; static const String activeCheck = '$svgBasePath/active-check.svg'; static const String deleteIcon = '$svgBasePath/delete_icon.svg'; + static const String call_ambulance_icon = '$svgBasePath/call_ambulance_icon.svg'; + static const String nearest_er_icon = '$svgBasePath/nearest_er_icon.svg'; + static const String rrt_icon = '$svgBasePath/rrt_icon.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/core/location_util.dart b/lib/core/location_util.dart index db8bef4..61c581e 100644 --- a/lib/core/location_util.dart +++ b/lib/core/location_util.dart @@ -1,16 +1,20 @@ import 'dart:io'; import 'dart:ui'; +import 'package:easy_localization/easy_localization.dart'; import 'package:geolocator/geolocator.dart'; import 'package:gms_check/gms_check.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:huawei_location/huawei_location.dart' as HmsLocation show FusedLocationProviderClient, Location, LocationSettingsRequest, LocationRequest; import 'package:location/location.dart' show Location, PermissionStatus, LocationData; -import 'package:permission_handler/permission_handler.dart' show Permission, PermissionListActions, PermissionStatusGetters; +import 'package:permission_handler/permission_handler.dart' show Permission, PermissionListActions, PermissionStatusGetters, openAppSettings; class LocationUtils { NavigationService navigationService; @@ -68,6 +72,26 @@ class LocationUtils { onFailure?.call(); return; } + } else if (permissionGranted == LocationPermission.deniedForever) { + if (isShowConfirmDialog) { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: navigationService.navigatorKey.currentContext!), + navigationService.navigatorKey.currentContext!, + child: Utils.getWarningWidget( + loadingText: "Please grant location permission from app settings to see the nearest ER location details".needTranslation, + isShowActionButtons: true, + onCancelTap: () { + navigationService.pop(); + }, + onConfirmTap: () async { + navigationService.pop(); + openAppSettings(); + }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } } Position? currentLocation = await Geolocator.getLastKnownPosition(); diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 4432737..79300fc 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -64,9 +64,11 @@ extension EmailValidator on String { ), ); - Widget toText12({Color? color, bool isUnderLine = false, bool isBold = false, FontWeight? fontWeight, bool isCenter = false, double? height, int maxLine = 0}) => Text( + Widget toText12( + {Color? color, bool isUnderLine = false, TextAlign textAlignment = TextAlign.start, bool isBold = false, FontWeight? fontWeight, bool isCenter = false, double? height, int maxLine = 0}) => + Text( this, - textAlign: isCenter ? TextAlign.center : null, + textAlign: isCenter ? TextAlign.center : textAlignment, maxLines: (maxLine > 0) ? maxLine : null, style: TextStyle( fontSize: 12.fSize, diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 02b0723..8be2b8e 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -457,6 +457,7 @@ class AuthenticationViewModel extends ChangeNotifier { int? patientID, }) async { bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true || _appState.getUserRegistrationPayload.patientOutSa == 1); + MyAppointmentsViewModel myAppointmentsVM = getIt(); final request = RequestUtils.getCommonRequestWelcome( phoneNumber: phoneNumberController.text, @@ -587,6 +588,7 @@ class AuthenticationViewModel extends ChangeNotifier { } // _appState.setUserBloodGroup = (activation.patientBlodType ?? ""); _appState.setAppAuthToken = activation.authenticationTokenId; + myAppointmentsVM.getActiveAppointmentsCount(); final request = RequestUtils.getAuthanticatedCommonRequest().toJson(); bool isUserAgreedBefore = await checkIfUserAgreedBefore(request: request); @@ -607,7 +609,6 @@ class AuthenticationViewModel extends ChangeNotifier { LoaderBottomSheet.hideLoader(); navigateToHomeScreen(); } else { - MyAppointmentsViewModel myAppointmentsVM = getIt(); myAppointmentsVM.setIsAppointmentDataToBeLoaded(true); LoaderBottomSheet.hideLoader(); navigateToHomeScreen(); diff --git a/lib/features/insurance/insurance_view_model.dart b/lib/features/insurance/insurance_view_model.dart index 363ad28..8460bd4 100644 --- a/lib/features/insurance/insurance_view_model.dart +++ b/lib/features/insurance/insurance_view_model.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart'; import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_approval_response_model.dart'; import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_card_history.dart'; @@ -14,9 +15,10 @@ class InsuranceViewModel extends ChangeNotifier { bool isInsuranceUpdateDetailsLoading = false; bool isInsuranceDataToBeLoaded = true; - bool isInsuranceApprovalsLoading = false; + bool isInsuranceExpired = false; + InsuranceRepo insuranceRepo; ErrorHandlerService errorHandlerService; @@ -85,6 +87,11 @@ class InsuranceViewModel extends ChangeNotifier { patientInsuranceList = apiResponse.data!; isInsuranceLoading = false; isInsuranceDataToBeLoaded = false; + + isInsuranceExpired = DateTime.now().isAfter( + DateUtil.convertStringToDate(patientInsuranceList.first.cardValidTo), + ); + notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); diff --git a/lib/features/lab/lab_repo.dart b/lib/features/lab/lab_repo.dart index 2618ab6..3bb793b 100644 --- a/lib/features/lab/lab_repo.dart +++ b/lib/features/lab/lab_repo.dart @@ -3,6 +3,8 @@ import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_special_result.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -13,13 +15,12 @@ abstract class LabRepo { Future>>> getPatientLabOrders(); Future>>> getPatientLabResults(PatientLabOrdersResponseModel laborder, bool isVidaPlus, String procedureName); - Future>>> - getPatientLabResultsByHospitals( - PatientLabOrdersResponseModel laborder, bool isVidaPlus); + Future>>> getPatientLabResultsByHospitals(PatientLabOrdersResponseModel laborder, bool isVidaPlus); + + Future>>> getSpecialLabResult(PatientLabOrdersResponseModel laborder, bool isVidaPlus); + + Future>> getLabResultReportPDF({required PatientLabOrdersResponseModel labOrder}); - Future>>> - getSpecialLabResult( - PatientLabOrdersResponseModel laborder, bool isVidaPlus); } class LabRepoImp implements LabRepo { @@ -223,4 +224,58 @@ class LabRepoImp implements LabRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> getLabResultReportPDF({required PatientLabOrdersResponseModel labOrder}) async { + Map mapDevice = { + "InvoiceNo": Utils.isVidaPlusProject(int.parse(labOrder.projectID!)) ? "0" : labOrder.invoiceNo, + "InvoiceNo_VP": Utils.isVidaPlusProject(int.parse(labOrder.projectID!)) ? labOrder.invoiceNo : "0", + // "LineItemNo": labOrder.invoiceLineItemNo, + // "InvoiceLineItemNo": labOrder.invoiceLineItemNo, + "ProjectID": labOrder.projectID!, + "DoctorID": labOrder.doctorID!, + "OrderNo": labOrder.orderNo!, + "InvoiceType": labOrder.invoiceType!, + "SetupID": labOrder.setupID!, + "IsDownload": true, + 'ClinicName': labOrder.clinicDescription, + 'DateofBirth': Utils.appState.getAuthenticatedUser()!.dateofBirth, + 'DoctorName': labOrder.doctorName, + 'OrderDate': '${DateUtil.convertStringToDate(labOrder.orderDate!).year}-${DateUtil.convertStringToDate(labOrder.orderDate!).month}-${DateUtil.convertStringToDate(labOrder.orderDate!).day}', + 'PatientIditificationNum': Utils.appState.getAuthenticatedUser()!.patientIdentificationNo, + 'PatientMobileNumber': Utils.appState.getAuthenticatedUser()!.mobileNumber, + 'PatientName': "${Utils.appState.getAuthenticatedUser()!.firstName!} ${Utils.appState.getAuthenticatedUser()!.lastName!}", + 'ProjectName': labOrder.projectName, + "To": Utils.appState.getAuthenticatedUser()!.emailAddress + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + Utils.isVidaPlusProject(int.parse(labOrder.projectID!)) ? SEND_LAB_RESULT_EMAIL : SEND_LAB_RESULT_EMAIL_NEW, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response["PdfContent"], + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index 63ba63d..12f0f27 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -36,6 +36,7 @@ class LabViewModel extends ChangeNotifier { List tempLabOrdersList = []; String labSpecialResult = ""; List labOrderTests = []; + String patientLabResultReportPDFBase64 = ""; PatientLabOrdersResponseModel? currentlySelectedPatientOrder; @@ -130,6 +131,31 @@ class LabViewModel extends ChangeNotifier { ); } + Future getLabResultReportPDF({required PatientLabOrdersResponseModel labOrder, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await labRepo.getLabResultReportPDF(labOrder: labOrder); + + result.fold( + (failure) async => await errorHandlerService.handleError( + failure: failure, + onOkPressed: () { + onError!(failure.message); + }, + ), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientLabResultReportPDFBase64 = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + filterSuggestions() { final List labels = patientLabOrders .expand((order) => order.testDetails!) diff --git a/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart b/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart index 4143498..8bef76a 100644 --- a/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart +++ b/lib/features/lab/models/resp_models/patient_lab_orders_response_model.dart @@ -155,10 +155,10 @@ class PatientLabOrdersResponseModel { status = json['Status']; statusDesc = json['StatusDesc']; strOrderDate = json['StrOrderDate']; - if (json['TestDetails'] != dynamic) { + if (json['TestDetails'] != null) { testDetails = []; json['TestDetails'].forEach((v) { - testDetails!.add(new TestDetails.fromJson(v)); + testDetails!.add(TestDetails.fromJson(v)); }); } } diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index db2d7fe..4b5a14c 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -382,7 +382,10 @@ class MedicalFileViewModel extends ChangeNotifier { final result = await medicalFileRepo.getAllPendingRecordsByResponseId(request: {'ResponseID': appState.getAuthenticatedUser()!.patientId ?? "0", "Status": 2}); result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), + // (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) async { + log("Error in fetching pending family files: ${failure.message}"); + }, (apiResponse) { if (apiResponse.messageStatus == 2) { // _dialogService.showErrorBottomSheet(message: apiResponse.errorMessage!, onOkPressed: () {}); diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index b94f004..87451c2 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -47,6 +47,8 @@ abstract class MyAppointmentsRepo { Future>> insertLiveCareVIDARequest({required clientRequestID, required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}); Future>> getTamaraInstallmentsDetails(); + + Future>> getActiveAppointmentsCount(); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -476,9 +478,6 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { final list = response['PatientDoctorAppointmentResultList']; - // if (list == null || list.isEmpty) { - // throw Exception("Appointments list is empty"); - // } final appointmentsList = list.map((item) => PatientAppointmentHistoryResponseModel.fromJson(item as Map)).toList().cast(); @@ -583,4 +582,40 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> getActiveAppointmentsCount() async { + Map mapDevice = {}; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_ACTIVE_APPOINTMENTS_LIST_URL, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final appointmentCount = response['AppointmentActiveNumber']; + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: appointmentCount, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 60fbff2..0da9b7a 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -17,6 +17,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { ErrorHandlerService errorHandlerService; AppState appState; + int activeAppointmentsCount = 0; + bool isMyAppointmentsLoading = false; bool isAppointmentPatientShareLoading = false; bool isTimeLineAppointmentsLoading = false; @@ -64,7 +66,6 @@ class MyAppointmentsViewModel extends ChangeNotifier { isMyAppointmentsLoading = true; isTimeLineAppointmentsLoading = true; patientMyDoctorsList.clear(); - isPatientMyDoctorsLoading = true; } isTamaraDetailsLoading = true; isAppointmentPatientShareLoading = true; @@ -121,10 +122,12 @@ class MyAppointmentsViewModel extends ChangeNotifier { patientArrivedAppointmentsHistoryList.clear(); notifyListeners(); - final result = await myAppointmentsRepo.getPatientAppointments(isActiveAppointment: isActiveAppointment, isArrivedAppointments: isArrivedAppointments); - final resultArrived = await myAppointmentsRepo.getPatientAppointments(isActiveAppointment: false, isArrivedAppointments: true); + final results = await Future.wait([ + myAppointmentsRepo.getPatientAppointments(isActiveAppointment: isActiveAppointment, isArrivedAppointments: isArrivedAppointments), + myAppointmentsRepo.getPatientAppointments(isActiveAppointment: false, isArrivedAppointments: true), + ]); - result.fold( + results[0].fold( (failure) async => await errorHandlerService.handleError(failure: failure), (apiResponse) { if (apiResponse.messageStatus == 2) { @@ -141,7 +144,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { }, ); - resultArrived.fold( + results[1].fold( (failure) async => await errorHandlerService.handleError(failure: failure), (apiResponse) { if (apiResponse.messageStatus == 2) { @@ -149,6 +152,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { } else if (apiResponse.messageStatus == 1) { patientArrivedAppointmentsHistoryList = apiResponse.data!; isMyAppointmentsLoading = false; + isAppointmentDataToBeLoaded = false; notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -363,6 +367,9 @@ class MyAppointmentsViewModel extends ChangeNotifier { Future getPatientMyDoctors({Function(dynamic)? onSuccess, Function(String)? onError}) async { if (!isAppointmentDataToBeLoaded) return; + isPatientMyDoctorsLoading = true; + notifyListeners(); + final result = await myAppointmentsRepo.getPatientDoctorsList(); result.fold( @@ -527,17 +534,26 @@ class MyAppointmentsViewModel extends ChangeNotifier { if (onSuccess != null) { onSuccess(apiResponse); } + }, + ); + } - // if (apiResponse.messageStatus == 2) { - // onError!(apiResponse.errorMessage!); - // // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); - // } else if (apiResponse.messageStatus == 1) { - // getTamaraInstallmentsDetailsResponseModel = apiResponse.data!; - // notifyListeners(); - // if (onSuccess != null) { - // onSuccess(apiResponse); - // } - // } + Future getActiveAppointmentsCount({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.getActiveAppointmentsCount(); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + activeAppointmentsCount = 0; + } else if (apiResponse.messageStatus == 1) { + activeAppointmentsCount = apiResponse.data ?? 0; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } }, ); } diff --git a/lib/features/prescriptions/prescriptions_repo.dart b/lib/features/prescriptions/prescriptions_repo.dart index 5f2db30..2e3f1aa 100644 --- a/lib/features/prescriptions/prescriptions_repo.dart +++ b/lib/features/prescriptions/prescriptions_repo.dart @@ -3,6 +3,7 @@ import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/prescriptions/models/resp_models/patient_prescriptions_response_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/models/resp_models/prescription_detail_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -11,6 +12,10 @@ abstract class PrescriptionsRepo { Future>>> getPatientPrescriptionOrders({required String patientId}); Future>>> getPatientPrescriptionDetails({required PatientPrescriptionsResponseModel prescriptionsResponseModel}); + + Future>> getPrescriptionInstructionsPDF({required PatientPrescriptionsResponseModel prescriptionsResponseModel}); + + Future>> getPrescriptionPDF({required PatientPrescriptionsResponseModel prescriptionsResponseModel, required List prescriptionDetailsList}); } class PrescriptionsRepoImp implements PrescriptionsRepo { @@ -107,4 +112,98 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> getPrescriptionInstructionsPDF({required PatientPrescriptionsResponseModel prescriptionsResponseModel}) async { + Map mapDevice = { + "AppointmentNo": prescriptionsResponseModel.appointmentNo.toString(), + "SetupID": prescriptionsResponseModel.setupID, + "ClinicID": prescriptionsResponseModel.clinicID.toString(), + "ProjectID": prescriptionsResponseModel.projectID.toString(), + "LocationID": "0", + "SalesInvoiceNo": "0", + "IsTest": false, + "ChannelID": "3", + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_PRESCRIPTION_INSTRUCTIONS_PDF, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final prescriptionPDFURL = response["InvoiceUrl"]; + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: prescriptionPDFURL, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future> getPrescriptionPDF({required PatientPrescriptionsResponseModel prescriptionsResponseModel, required List prescriptionDetailsList}) async { + Map mapDevice = { + "AppointmentDate": prescriptionsResponseModel.appointmentDate, + "ClinicName": prescriptionsResponseModel.clinicDescription, + "DoctorName": prescriptionsResponseModel.doctorName, + "ProjectID": prescriptionsResponseModel.projectID, + "DoctorID": prescriptionsResponseModel.doctorID, + "ClinicID": prescriptionsResponseModel.clinicID, + "DateofBirth": Utils.appState.getAuthenticatedUser()!.dateofBirth, + "ListPrescriptions": prescriptionDetailsList, + "PatientIditificationNum": Utils.appState.getAuthenticatedUser()!.patientIdentificationNo, + "PatientMobileNumber": Utils.appState.getAuthenticatedUser()!.mobileNumber, + "PatientName": "${Utils.appState.getAuthenticatedUser()!.firstName!} ${Utils.appState.getAuthenticatedUser()!.lastName!}", + "To": Utils.appState.getAuthenticatedUser()!.emailAddress, + "SetupID": prescriptionsResponseModel.setupID, + "IsDownload": true, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + SEND_PRESCRIPTION_EMAIL, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final prescriptionPDFData = response["Base64Data"]; + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: prescriptionPDFData, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index ebc3be8..aac25c1 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -23,6 +23,10 @@ class PrescriptionsViewModel extends ChangeNotifier { bool isSortByClinic = true; + String prescriptionInstructionsPDFLink = ""; + + String prescriptionPDFBase64Data = ""; + PrescriptionsViewModel({required this.prescriptionsRepo, required this.errorHandlerService}); initPrescriptionsViewModel() { @@ -126,4 +130,47 @@ class PrescriptionsViewModel extends ChangeNotifier { }, ); } + + Future getPrescriptionInstructionsPDF(PatientPrescriptionsResponseModel prescriptionsResponseModel, {Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await prescriptionsRepo.getPrescriptionInstructionsPDF(prescriptionsResponseModel: prescriptionsResponseModel); + + result.fold( + (failure) async { + onError!(failure.message); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + prescriptionInstructionsPDFLink = apiResponse.data; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future getPrescriptionPDFBase64(PatientPrescriptionsResponseModel prescriptionsResponseModel, {Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await prescriptionsRepo.getPrescriptionPDF(prescriptionsResponseModel: prescriptionsResponseModel, prescriptionDetailsList: prescriptionDetailsList); + + result.fold( + (failure) async { + onError!(failure.message); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + } else if (apiResponse.messageStatus == 1) { + prescriptionPDFBase64Data = apiResponse.data; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index c87039c..fe93ec3 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -86,30 +86,25 @@ class _AppointmentDetailsPageState extends State { onAskDoctorTap: () {}, onCancelTap: () async { myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); - showCommonBottomSheet(context, - child: Utils.getLoadingWidget(), - callBackFunc: (str) {}, - title: "", - height: ResponsiveExtension.screenHeight * 0.3, - isCloseButtonVisible: false, - isDismissible: false, - isFullScreen: false); + LoaderBottomSheet.showLoader(loadingText: "Cancelling Appointment, Please Wait...".needTranslation); await myAppointmentsViewModel.cancelAppointment( patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onSuccess: (apiResponse) { - Navigator.of(context).pop(); - showCommonBottomSheet(context, - child: Utils.getSuccessWidget(loadingText: "Appointment Cancelled Successfully".needTranslation), - callBackFunc: (str) {}, - title: "", - height: ResponsiveExtension.screenHeight * 0.3, - isCloseButtonVisible: false, - isDismissible: false, - isFullScreen: false, - isSuccessDialog: true); + LoaderBottomSheet.hideLoader(); + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.getPatientAppointments(true, false); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getSuccessWidget(loadingText: "Appointment Cancelled Successfully".needTranslation), + callBackFunc: () { + Navigator.of(context).pop(); + }, + title: "", + isCloseButtonVisible: true, + isDismissible: false, + isFullScreen: false, + ); }); - Navigator.of(context).pop(); - Navigator.of(context).pop(); }, onRescheduleTap: () async { openDoctorScheduleCalendar(); @@ -510,7 +505,7 @@ class _AppointmentDetailsPageState extends State { projectName: widget.patientAppointmentHistoryResponseModel.projectName, ); bookAppointmentsViewModel.setSelectedDoctor(doctor); - LoaderBottomSheet.showLoader(); + LoaderBottomSheet.showLoader(loadingText: "Fetching Doctor Schedule, Please Wait...".needTranslation); await bookAppointmentsViewModel.getDoctorFreeSlots( isBookingForLiveCare: false, onSuccess: (dynamic respData) async { @@ -541,24 +536,19 @@ class _AppointmentDetailsPageState extends State { case 0: break; case 10: - showCommonBottomSheet(context, - child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); + LoaderBottomSheet.showLoader(loadingText: "Confirming Appointment, Please Wait...".needTranslation); await myAppointmentsViewModel.confirmAppointment( patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onSuccess: (apiResponse) { - Navigator.of(context).pop(); - showCommonBottomSheet(context, - child: Utils.getSuccessWidget(loadingText: "Appointment Confirmed Successfully".needTranslation), - callBackFunc: (str) {}, - title: "", - height: ResponsiveExtension.screenHeight * 0.3, - isCloseButtonVisible: false, - isDismissible: false, - isFullScreen: false, + LoaderBottomSheet.hideLoader(); + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.getPatientAppointments(true, false); + showCommonBottomSheet(context, child: Utils.getSuccessWidget(loadingText: "Appointment Confirmed Successfully".needTranslation), callBackFunc: (str) { + Navigator.of(context).pop(); + }, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: true, isDismissible: false, isFullScreen: false, isSuccessDialog: true); }); - Navigator.of(context).pop(); - Navigator.of(context).pop(); + // LoaderBottomSheet.hideLoader(); case 15: break; case 20: diff --git a/lib/presentation/appointments/my_doctors_page.dart b/lib/presentation/appointments/my_doctors_page.dart index 06257c7..51e9703 100644 --- a/lib/presentation/appointments/my_doctors_page.dart +++ b/lib/presentation/appointments/my_doctors_page.dart @@ -8,10 +8,16 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; import '../../widgets/chip/app_custom_chip_widget.dart'; @@ -20,11 +26,13 @@ class MyDoctorsPage extends StatelessWidget { MyDoctorsPage({super.key}); late MyAppointmentsViewModel myAppointmentsViewModel; + late BookAppointmentsViewModel bookAppointmentsViewModel; @override Widget build(BuildContext context) { AppState appState = getIt.get(); myAppointmentsViewModel = Provider.of(context, listen: false); + bookAppointmentsViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( @@ -141,13 +149,38 @@ class MyDoctorsPage extends StatelessWidget { "".toText16(), Transform.flip( flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 15.h, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor)), + child: + Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon_small, width: 15.h, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor)), ], ), ], ), ), - ), + ).onPress(() async { + bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel( + clinicID: myAppointmentsVM.patientMyDoctorsList[index].clinicID, + projectID: myAppointmentsVM.patientMyDoctorsList[index].projectID, + doctorID: myAppointmentsVM.patientMyDoctorsList[index].doctorID, + )); + LoaderBottomSheet.showLoader(); + await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) { + LoaderBottomSheet.hideLoader(); + Navigator.of(context).push( + CustomPageRoute( + page: DoctorProfilePage(), + ), + ); + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }), ), ), ); diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index aa67a76..1451bcb 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -117,26 +117,26 @@ class _AppointmentCardState extends State { ).toShimmer2(isShow: widget.isLoading), ), // TODO: Implement the logic to enable/disable the switch based on reminder status - AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) - ? SizedBox().toShimmer2(isShow: widget.isLoading) - : Switch( - activeColor: AppColors.successColor, - activeTrackColor: AppColors.successColor.withValues(alpha: .15), - thumbIcon: WidgetStateProperty.resolveWith( - (Set states) { - if (states.contains(WidgetState.selected)) { - return const Icon(Icons.check); // Icon when switch is ON - } - return const Icon(Icons.close); // Icon when switch is OFF - }, - ), - value: widget.isLoading ? false : widget.patientAppointmentHistoryResponseModel.hasReminder!, - onChanged: (newValue) { - setState(() { - widget.myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); - }); - }, - ).toShimmer2(isShow: widget.isLoading), + // AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + // ? SizedBox().toShimmer2(isShow: widget.isLoading) + // : Switch( + // activeColor: AppColors.successColor, + // activeTrackColor: AppColors.successColor.withValues(alpha: .15), + // thumbIcon: WidgetStateProperty.resolveWith( + // (Set states) { + // if (states.contains(WidgetState.selected)) { + // return const Icon(Icons.check); // Icon when switch is ON + // } + // return const Icon(Icons.close); // Icon when switch is OFF + // }, + // ), + // value: widget.isLoading ? false : widget.patientAppointmentHistoryResponseModel.hasReminder!, + // onChanged: (newValue) { + // setState(() { + // widget.myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); + // }); + // }, + // ).toShimmer2(isShow: widget.isLoading), ], ), SizedBox(height: 16.h), @@ -155,7 +155,7 @@ class _AppointmentCardState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ (widget.isLoading ? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png" : widget.patientAppointmentHistoryResponseModel.doctorNameObj!) - .toText16(isBold: true) + .toText16(isBold: true, maxlines: 1) .toShimmer2(isShow: widget.isLoading), SizedBox(height: 8.h), Wrap( diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index f5e534e..5e5c79b 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -303,7 +303,12 @@ class _SelectClinicPageState extends State { id = regionalViewModel.selectedHospital?.patientDoctorAppointmentList?.first.projectID?.toString() ?? ""; } if (bookAppointmentsViewModel.selectedClinic.clinicID == 17) { - if (!appState.isAuthenticated) { + + if (appState.isAuthenticated) { + initDentalAppointmentBookingFlow(int.parse(bookAppointmentsViewModel + .currentlySelectedHospitalFromRegionFlow ?? + "0")); + }else { bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true); } } @@ -459,7 +464,11 @@ class _SelectClinicPageState extends State { isCloseButtonVisible: true, ); } else { - // Navigate to Chief Complaint Screen + Navigator.of(context).push( + CustomPageRoute( + page: DentalChiefComplaintsPage(), + ), + ); } }); } diff --git a/lib/presentation/emergency_services/emergency_services_page.dart b/lib/presentation/emergency_services/emergency_services_page.dart new file mode 100644 index 0000000..0052930 --- /dev/null +++ b/lib/presentation/emergency_services/emergency_services_page.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/location_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/nearest_er_page.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; + +class EmergencyServicesPage extends StatelessWidget { + EmergencyServicesPage({super.key}); + + LocationUtils? locationUtils; + + @override + Widget build(BuildContext context) { + locationUtils = getIt.get(); + locationUtils!.isShowConfirmDialog = true; + return CollapsingListView( + title: "Emergency Services", + requests: () {}, + child: Padding( + padding: EdgeInsets.all(24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.call_ambulance_icon, width: 40.h, height: 40.h), + SizedBox(width: 12.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Call Ambulance".needTranslation.toText16(isBold: true, color: AppColors.blackColor), + "Request and ambulance in emergency from home or hospital".needTranslation.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + ], + ), + ), + SizedBox(width: 12.h), + Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, width: 13.h, height: 13.h), + ], + ), + ), + SizedBox(height: 16.h), + Container( + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.nearest_er_icon, width: 40.h, height: 40.h), + SizedBox(width: 12.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Nearest ER Location".needTranslation.toText16(isBold: true, color: AppColors.blackColor), + "Get the details of nearest branch including directions".needTranslation.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + ], + ), + ), + SizedBox(width: 12.h), + Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, width: 13.h, height: 13.h), + ], + ).onPress(() { + locationUtils!.getLocation(onSuccess: (position) { + Navigator.of(context).push( + CustomPageRoute( + page: NearestErPage(), + ), + ); + }); + }), + ), + SizedBox(height: 16.h), + Container( + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.rrt_icon, width: 40.h, height: 40.h), + SizedBox(width: 12.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Rapid Response Team (RRT)".toText16(isBold: true, color: AppColors.blackColor), + "Comprehensive medical service for all sorts of urgent and stable cases".toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + ], + ), + ), + SizedBox(width: 12.h), + Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, width: 13.h, height: 13.h), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/emergency_services/nearest_er_page.dart b/lib/presentation/emergency_services/nearest_er_page.dart new file mode 100644 index 0000000..ebf1c91 --- /dev/null +++ b/lib/presentation/emergency_services/nearest_er_page.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; + +class NearestErPage extends StatefulWidget { + const NearestErPage({super.key}); + + @override + State createState() => _NearestErPageState(); +} + +class _NearestErPageState extends State { + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: "Nearest ER", + child: Container(), + ); + } +} diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart new file mode 100644 index 0000000..f79aae0 --- /dev/null +++ b/lib/presentation/hmg_services/services_page.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; + +class ServicesPage extends StatelessWidget { + const ServicesPage({super.key}); + + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: "Explore Services".needTranslation, + isLeading: false, + child: Padding( + padding: EdgeInsets.all(24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Medical & Care Services".needTranslation.toText18(isBold: true) + ], + ), + ), + ); + } +} diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 7de153f..f9665b9 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -436,6 +436,7 @@ class _LandingPageState extends State { horizontalOffset: 100.0, child: FadeInAnimation( child: SmallServiceCard( + serviceName: LandingPageData.getNotLoggedInServiceCardsList[index].serviceName, icon: LandingPageData.getNotLoggedInServiceCardsList[index].icon, title: LandingPageData.getNotLoggedInServiceCardsList[index].title, subtitle: LandingPageData.getNotLoggedInServiceCardsList[index].subtitle, diff --git a/lib/presentation/home/navigation_screen.dart b/lib/presentation/home/navigation_screen.dart index 6b33b2f..c2bfae7 100644 --- a/lib/presentation/home/navigation_screen.dart +++ b/lib/presentation/home/navigation_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; +import 'package:hmg_patient_app_new/presentation/hmg_services/services_page.dart'; import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/widgets/bottom_navigation/bottom_navigation.dart'; @@ -30,7 +31,7 @@ class _LandingNavigationState extends State { appState.isAuthenticated ? MedicalFilePage() : /* need add feedback page */ const LandingPage(), BookAppointmentPage(), const LandingPage(), - appState.isAuthenticated ? /* need add news page */ LandingPage() : const LandingPage(), + appState.isAuthenticated ? /* need add news page */ ServicesPage() : const LandingPage(), ], ), bottomNavigationBar: BottomNavigation( diff --git a/lib/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index 416ed9a..ee5fcca 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -3,6 +3,7 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/emergency_services_page.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves_list_page.dart'; @@ -104,6 +105,13 @@ class SmallServiceCard extends StatelessWidget { ), ); break; + case "emergency": + Navigator.of(context).push( + CustomPageRoute( + page: EmergencyServicesPage(), + ), + ); + break; default: // Handle unknown service break; diff --git a/lib/presentation/insurance/widgets/insurance_approval_details_page.dart b/lib/presentation/insurance/insurance_approval_details_page.dart similarity index 82% rename from lib/presentation/insurance/widgets/insurance_approval_details_page.dart rename to lib/presentation/insurance/insurance_approval_details_page.dart index c9b1517..437c870 100644 --- a/lib/presentation/insurance/widgets/insurance_approval_details_page.dart +++ b/lib/presentation/insurance/insurance_approval_details_page.dart @@ -111,8 +111,6 @@ class InsuranceApprovalDetailsPage extends StatelessWidget { ), SizedBox(height: 16.h), Container( - width: double.infinity, - height: 200.h, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 24.h, @@ -124,6 +122,33 @@ class InsuranceApprovalDetailsPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ "Approval Details".toText16(isBold: true), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.procedureName.tr(context: context).toText14(isBold: true), + Expanded( + child: insuranceApprovalResponseModel.apporvalDetails!.procedureName! + .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, textAlignment: TextAlign.end, maxLine: 2), + ), + ], + ), + SizedBox(height: 8.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Status:".needTranslation.toText14(isBold: true), + insuranceApprovalResponseModel.apporvalDetails!.status!.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + ], + ), + SizedBox(height: 8.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "${LocaleKeys.usageStatus.tr(context: context)}: ".toText14(isBold: true), + insuranceApprovalResponseModel.apporvalDetails!.isInvoicedDesc!.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + ], + ), ], ), ), diff --git a/lib/presentation/insurance/insurance_approvals_page.dart b/lib/presentation/insurance/insurance_approvals_page.dart index 50e659c..b70c116 100644 --- a/lib/presentation/insurance/insurance_approvals_page.dart +++ b/lib/presentation/insurance/insurance_approvals_page.dart @@ -13,7 +13,7 @@ import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patient_insurance_approval_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_approval_card.dart'; -import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_approval_details_page.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/insurance_approval_details_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; diff --git a/lib/presentation/insurance/widgets/insurance_approval_card.dart b/lib/presentation/insurance/widgets/insurance_approval_card.dart index 71fce58..bea2e56 100644 --- a/lib/presentation/insurance/widgets/insurance_approval_card.dart +++ b/lib/presentation/insurance/widgets/insurance_approval_card.dart @@ -120,7 +120,7 @@ class InsuranceApprovalCard extends StatelessWidget { Transform.flip( flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon_small, width: 15.h, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor)), ], - ), + ).toShimmer2(isShow: isLoading), ], ), ), diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart index b89d3e8..5190e5b 100644 --- a/lib/presentation/insurance/widgets/patient_insurance_card.dart +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -43,6 +43,7 @@ class PatientInsuranceCard extends StatelessWidget { children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -51,20 +52,14 @@ class PatientInsuranceCard extends StatelessWidget { "Policy: ${insuranceCardDetailsModel.insurancePolicyNo}".toText12(isBold: true, color: AppColors.lightGrayColor), ], ), - CustomButton( + AppCustomChipWidget( icon: isInsuranceExpired ? AppAssets.cancel_circle_icon : AppAssets.insurance_active_icon, + labelText: isInsuranceExpired ? "Insurance Expired".needTranslation : "Insurance Active".needTranslation, iconColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, - iconSize: 13.h, - text: isInsuranceExpired ? "Insurance Expired".needTranslation : "Insurance Active".needTranslation, - onPressed: () {}, - backgroundColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.15) : AppColors.successColor.withOpacity(0.15), - borderColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.01) : AppColors.successColor.withOpacity(0.01), textColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, + iconSize: 12, + backgroundColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1), + labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), ), ], ), @@ -79,8 +74,10 @@ class PatientInsuranceCard extends StatelessWidget { children: [ AppCustomChipWidget( icon: AppAssets.doctor_calendar_icon, - labelText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}"), - AppCustomChipWidget(labelText: "Patient Card ID: ${insuranceCardDetailsModel.patientCardID}"), + labelText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}", + labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), + ), + AppCustomChipWidget(labelText: "Patient Card ID: ${insuranceCardDetailsModel.patientCardID}".needTranslation), ], ), SizedBox(height: 10.h), diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index ef8d77d..0b9d093 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -1 +1 @@ -import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import '../../widgets/appbar/collapsing_list_view.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: SingleChildScrollView( padding: EdgeInsets.all(24.h), physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), SizedBox(height: 16.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.isNotEmpty ? model.patientLabOrders.length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.patientLabOrders.isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabResultItemView( onTap: () { model.currentlySelectedPatientOrder = model.patientLabOrders[ index]; labProvider.getPatientLabResultByHospital(model.patientLabOrders[ index]); labProvider .getPatientSpecialResult( model.patientLabOrders[ index]); Navigator.push( context, CustomPageRoute( page: LabResultByClinic(), )); }, labOrder: model.patientLabOrders[index], index: index, isExpanded: isExpanded), ), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) : ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.uniqueTests.toList().isNotEmpty ? model.uniqueTests.toList().length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.uniqueTests.toList().isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabOrderByTest( appState: _appState, onTap: () { if (model.uniqueTests.toList()[index].model != null) { rangeViewModel.flush(); model.getPatientLabResult(model.uniqueTests.toList()[index].model!, model.uniqueTests.toList()[index].description!, (_appState.isArabic() ? model.uniqueTests.toList()[index].testDescriptionAr! : model.uniqueTests.toList()[index].testDescriptionEn!)); } }, tests: model.uniqueTests.toList()[index], index: index, isExpanded: isExpanded)), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) ], ); }, ), ), )); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file +import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import '../../widgets/appbar/collapsing_list_view.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: SingleChildScrollView( padding: EdgeInsets.all(24.h), physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), SizedBox(height: 16.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.isNotEmpty ? model.patientLabOrders.length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.patientLabOrders.isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabResultItemView( onTap: () { model.currentlySelectedPatientOrder = model.patientLabOrders[ index]; labProvider.getPatientLabResultByHospital(model.patientLabOrders[ index]); labProvider .getPatientSpecialResult( model.patientLabOrders[ index]); Navigator.push( context, CustomPageRoute( page: LabResultByClinic(labOrder: model.patientLabOrders[index]), )); }, labOrder: model.patientLabOrders[index], index: index, isExpanded: isExpanded), ), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) : ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.uniqueTests.toList().isNotEmpty ? model.uniqueTests.toList().length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.uniqueTests.toList().isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabOrderByTest( appState: _appState, onTap: () { if (model.uniqueTests.toList()[index].model != null) { rangeViewModel.flush(); model.getPatientLabResult(model.uniqueTests.toList()[index].model!, model.uniqueTests.toList()[index].description!, (_appState.isArabic() ? model.uniqueTests.toList()[index].testDescriptionAr! : model.uniqueTests.toList()[index].testDescriptionEn!)); } }, tests: model.uniqueTests.toList()[index], index: index, isExpanded: isExpanded)), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) ], ); }, ), ), )); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file diff --git a/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart b/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart index 45c718b..a603ecb 100644 --- a/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart +++ b/lib/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart @@ -2,68 +2,143 @@ import 'package:easy_localization/easy_localization.dart' show tr, StringTranslateExtension; import 'package:flutter/material.dart'; import 'package:flutter_widget_from_html/flutter_widget_from_html.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultList.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/lab_order_specialResult.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:open_filex/open_filex.dart'; import 'package:provider/provider.dart'; class LabResultByClinic extends StatelessWidget { + LabResultByClinic({required this.labOrder, Key? key}); + + late LabViewModel labViewModel; + PatientLabOrdersResponseModel labOrder; @override Widget build(BuildContext context) { - return CollapsingListView( - title: LocaleKeys.labResults.tr(), - child: SingleChildScrollView( - child: Column( - spacing: 8.h, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Selector( - selector: (_, model) => model.isLabResultByHospitalLoading, - builder: (_, isLoading, __) { - if (isLoading) { - return Column( - children: [ - LabResultItemView( - onTap: () {}, - labOrder: null, - index: 0, - isLoading: true, - ), - LabResultItemView( - onTap: () {}, - labOrder: null, - index: 0, - isLoading: true, - ), - LabResultItemView( - onTap: () {}, - labOrder: null, - index: 0, - isLoading: true, - ), - LabResultItemView( - onTap: () {}, - labOrder: null, - index: 0, - isLoading: true, - ), - ], - ); - } else { - return LabResultList(); + labViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: LocaleKeys.labResults.tr(), + child: SingleChildScrollView( + child: Column( + spacing: 8.h, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Selector( + selector: (_, model) => model.isLabResultByHospitalLoading, + builder: (_, isLoading, __) { + if (isLoading) { + return Column( + children: [ + LabResultItemView( + onTap: () {}, + labOrder: null, + index: 0, + isLoading: true, + ), + LabResultItemView( + onTap: () {}, + labOrder: null, + index: 0, + isLoading: true, + ), + LabResultItemView( + onTap: () {}, + labOrder: null, + index: 0, + isLoading: true, + ), + LabResultItemView( + onTap: () {}, + labOrder: null, + index: 0, + isLoading: true, + ), + ], + ); + } else { + return LabResultList(); + } + }, + ), + LabOrderSpecialResult() + ], + ).paddingAll(24.h), + )), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: CustomButton( + text: "Download report".needTranslation, + onPressed: () async { + LoaderBottomSheet.showLoader(loadingText: "Generating report, Please wait...".needTranslation); + await labViewModel + .getLabResultReportPDF( + labOrder: labOrder, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }) + .then((val) async { + LoaderBottomSheet.hideLoader(); + if (labViewModel.patientLabResultReportPDFBase64.isNotEmpty) { + String path = await Utils.createFileFromString(labViewModel.patientLabResultReportPDFBase64, "pdf"); + try { + OpenFilex.open(path); + } catch (ex) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Cannot open file".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } } - }, - ), - LabOrderSpecialResult() - ], - ).paddingAll(24.h), - )); + }); + }, + backgroundColor: AppColors.successColor, + borderColor: AppColors.successColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 45.h, + icon: AppAssets.download, + iconColor: AppColors.whiteColor, + iconSize: 20.h, + ).paddingSymmetrical(24.h, 24.h), + ), + ], + ), + ); } } diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 1eff927..7fc7115 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -26,6 +26,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_approvals_page.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; @@ -200,9 +201,21 @@ class _MedicalFilePageState extends State { ), AppCustomChipWidget( icon: AppAssets.blood_icon, - labelText: "${LocaleKeys.bloodType.tr(context: context)}: ${appState.getUserBloodGroup}", + labelText: "Blood: ${appState.getUserBloodGroup}", iconColor: AppColors.primaryRedColor, + labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), ), + Consumer(builder: (context, insuranceVM, child) { + return AppCustomChipWidget( + icon: insuranceVM.isInsuranceExpired ? AppAssets.cancel_circle_icon : AppAssets.insurance_active_icon, + labelText: insuranceVM.isInsuranceExpired ? "Insurance Expired".needTranslation : "Insurance Active".needTranslation, + iconColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, + textColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, + iconSize: 12, + backgroundColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1), + labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), + ); + }), ], ), ], @@ -515,7 +528,7 @@ class _MedicalFilePageState extends State { backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, - fontSize: 12.3, + fontSize: 12, fontWeight: FontWeight.w500, borderRadius: 12.h, height: 40.h, @@ -532,7 +545,7 @@ class _MedicalFilePageState extends State { backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, - fontSize: 13, + fontSize: 12, fontWeight: FontWeight.w500, borderRadius: 12.h, height: 40.h, @@ -569,7 +582,6 @@ class _MedicalFilePageState extends State { Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], ).onPress(() { - myAppointmentsViewModel.setIsPatientMyDoctorsLoading(true); myAppointmentsViewModel.getPatientMyDoctors(); Navigator.of(context).push( CustomPageRoute( @@ -639,7 +651,31 @@ class _MedicalFilePageState extends State { ), ], ), - ), + ).onPress(() async { + bookAppointmentsViewModel.setSelectedDoctor(DoctorsListResponseModel( + clinicID: myAppointmentsVM.patientMyDoctorsList[index].clinicID, + projectID: myAppointmentsVM.patientMyDoctorsList[index].projectID, + doctorID: myAppointmentsVM.patientMyDoctorsList[index].doctorID, + )); + LoaderBottomSheet.showLoader(); + await bookAppointmentsViewModel.getDoctorProfile(onSuccess: (dynamic respData) { + LoaderBottomSheet.hideLoader(); + Navigator.of(context).push( + CustomPageRoute( + page: DoctorProfilePage(), + ), + ); + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }), ), ), ) diff --git a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart index 87794ef..a818ae5 100644 --- a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart +++ b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart @@ -77,6 +77,7 @@ class PatientSickLeaveCard extends StatelessWidget { AppCustomChipWidget( icon: AppAssets.doctor_calendar_icon, labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientSickLeavesResponseModel.appointmentDate), false), + labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), ).toShimmer2(isShow: isLoading), AppCustomChipWidget(labelText: isLoading ? "Pending Activation" : patientSickLeavesResponseModel.clinicName!).toShimmer2(isShow: isLoading), ], diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index 9f6d55b..473f79a 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -20,8 +20,11 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:open_filex/open_filex.dart'; import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; class PrescriptionDetailPage extends StatefulWidget { PrescriptionDetailPage({super.key, required this.prescriptionsResponseModel}); @@ -59,6 +62,33 @@ class _PrescriptionDetailPageState extends State { Expanded( child: CollapsingListView( title: LocaleKeys.prescriptions.tr(context: context), + instructions: () async { + LoaderBottomSheet.showLoader(loadingText: "Fetching prescription PDF, Please wait...".needTranslation); + await prescriptionsViewModel.getPrescriptionInstructionsPDF(widget.prescriptionsResponseModel, onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + if (prescriptionsViewModel.prescriptionInstructionsPDFLink.isNotEmpty) { + Uri uri = Uri.parse(prescriptionsViewModel.prescriptionInstructionsPDFLink); + launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: ""); + } else { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Unable to fetch PDF".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, child: SingleChildScrollView( child: Consumer(builder: (context, prescriptionVM, child) { return Column( @@ -98,6 +128,7 @@ class _PrescriptionDetailPageState extends State { AppCustomChipWidget( icon: AppAssets.doctor_calendar_icon, labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), false), + labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), ), AppCustomChipWidget( labelText: widget.prescriptionsResponseModel.clinicDescription!, @@ -112,6 +143,41 @@ class _PrescriptionDetailPageState extends State { ), ], ), + SizedBox(height: 16.h), + CustomButton( + text: "Download Prescription".needTranslation, + onPressed: () async { + LoaderBottomSheet.showLoader(); + await prescriptionVM.getPrescriptionPDFBase64(widget.prescriptionsResponseModel).then((val) async { + LoaderBottomSheet.hideLoader(); + if (prescriptionVM.prescriptionPDFBase64Data.isNotEmpty) { + String path = await Utils.createFileFromString(prescriptionVM.prescriptionPDFBase64Data, "pdf"); + try { + OpenFilex.open(path); + } catch (ex) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Cannot open file".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + } + }); + }, + backgroundColor: AppColors.successColor.withValues(alpha: 0.15), + borderColor: AppColors.successColor.withValues(alpha: 0.01), + textColor: AppColors.successColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.download, + iconColor: AppColors.successColor, + iconSize: 14.h, + ), ], ), ), diff --git a/lib/presentation/prescriptions/prescription_item_view.dart b/lib/presentation/prescriptions/prescription_item_view.dart index 8357033..acf03f5 100644 --- a/lib/presentation/prescriptions/prescription_item_view.dart +++ b/lib/presentation/prescriptions/prescription_item_view.dart @@ -209,21 +209,7 @@ class PrescriptionItemView extends StatelessWidget { height: 40.h, ).toShimmer2(isShow: isLoading), ), - SizedBox(width: 16.h), - Expanded( - child: CustomButton( - text: LocaleKeys.readInstructions.tr(context: context), - onPressed: () {}, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: AppColors.whiteColor, - fontSize: 13, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ).toShimmer2(isShow: isLoading), - ), + // SizedBox(width: 16.h), ], ).paddingSymmetrical(16.h, 16.h), ], diff --git a/lib/presentation/radiology/radiology_result_page.dart b/lib/presentation/radiology/radiology_result_page.dart index e15c8d6..df34164 100644 --- a/lib/presentation/radiology/radiology_result_page.dart +++ b/lib/presentation/radiology/radiology_result_page.dart @@ -68,7 +68,7 @@ class _RadiologyResultPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), - widget.patientRadiologyResponseModel.description!.toText16(isBold: true), + // widget.patientRadiologyResponseModel.description!.toText16(isBold: true), SizedBox(height: 8.h), widget.patientRadiologyResponseModel.reportData!.trim().toText12(isBold: true, color: AppColors.textColorLight), SizedBox(height: 16.h), diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index 2687944..d4f89cc 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -21,12 +21,15 @@ class CollapsingListView extends StatelessWidget { VoidCallback? report; VoidCallback? logout; VoidCallback? history; + VoidCallback? instructions; + VoidCallback? requests; Widget? bottomChild; Widget? trailing; bool isClose; bool isLeading; - CollapsingListView({required this.title, this.child, this.search, this.isClose = false, this.bottomChild, this.report, this.logout, this.history, this.isLeading = true, this.trailing}); + CollapsingListView( + {required this.title, this.child, this.search, this.isClose = false, this.bottomChild, this.report, this.logout, this.history, this.instructions, this.requests, this.isLeading = true, this.trailing}); @override Widget build(BuildContext context) { @@ -98,6 +101,8 @@ class CollapsingListView extends StatelessWidget { if (logout != null) actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(logout!), if (report != null) actionButton(context, t, title: "Report".needTranslation, icon: AppAssets.report_icon).onPress(report!), if (history != null) actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon).onPress(history!), + if (instructions != null) actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(instructions!), + if (requests != null) actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon).onPress(requests!), if (search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(search!).paddingOnly(right: 24), if (trailing != null) trailing!, ], diff --git a/lib/widgets/chip/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart index 2cfd5ce..8facd92 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -58,7 +58,7 @@ class AppCustomChipWidget extends StatelessWidget { color: Colors.transparent, // Crucially, set color to transparent style: BorderStyle.none, ), - borderRadius: BorderRadius.circular(8.0), // Apply a border radius of 16.0 + borderRadius: BorderRadius.circular(8.h), // Apply a border radius of 16.0 ), ), child: icon.isNotEmpty