From 100d4bda9ff800041db4539cbe1605138cfdfbf7 Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Sun, 9 Nov 2025 21:28:07 +0300 Subject: [PATCH 1/3] Completed Ancillary flow with CardPayments --- lib/core/api/api_client.dart | 69 +- lib/core/api_consts.dart | 31 +- lib/core/dependencies.dart | 33 +- lib/core/utils/request_utils.dart | 38 +- lib/core/utils/utils.dart | 19 +- .../authentication_view_model.dart | 2 +- .../emergency_services_view_model.dart | 4 +- .../appointment_via_region_viewmodel.dart | 2 +- lib/features/payfort/payfort_view_model.dart | 31 +- lib/features/radiology/radiology_repo.dart | 23 +- .../radiology/radiology_view_model.dart | 15 +- .../ancillary_order_list_response_model.dart | 109 +++ ...rder_procedures_detail_response_model.dart | 221 ++++++ .../todo_section/todo_section_repo.dart | 377 +++++++++++ .../todo_section/todo_section_view_model.dart | 244 +++++++ lib/main.dart | 4 + .../appointment_payment_page.dart | 109 +-- .../widgets/appointment_doctor_card.dart | 13 +- .../search_doctor_by_name.dart | 2 +- .../book_appointment/widgets/doctor_card.dart | 25 +- lib/presentation/home/navigation_screen.dart | 2 +- .../medical_file/medical_file_page.dart | 14 +- .../medical_file_appointment_card.dart | 5 +- .../widgets/medical_file_card.dart | 21 +- .../onboarding/splash_animation_screen.dart | 2 +- .../radiology/radiology_orders_page.dart | 231 +++---- lib/presentation/todo/todo_page.dart | 31 - .../ancillary_order_payment_page.dart | 484 ++++++++++++++ .../ancillary_procedures_details_page.dart | 630 ++++++++++++++++++ lib/presentation/todo_section/todo_page.dart | 88 +++ .../widgets/ancillary_orders_list.dart | 276 ++++++++ .../widgets/ancillary_procedures_list.dart | 274 ++++++++ lib/services/analytics/flows/app_nav.dart | 2 +- lib/services/analytics/flows/todo_list.dart | 2 +- lib/services/cache_service.dart | 4 +- lib/services/error_handler_service.dart | 22 +- lib/services/logger_service.dart | 4 +- lib/widgets/buttons/custom_button.dart | 6 +- 38 files changed, 3138 insertions(+), 331 deletions(-) create mode 100644 lib/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart create mode 100644 lib/features/todo_section/models/resp_models/ancillary_order_procedures_detail_response_model.dart create mode 100644 lib/features/todo_section/todo_section_repo.dart create mode 100644 lib/features/todo_section/todo_section_view_model.dart delete mode 100644 lib/presentation/todo/todo_page.dart create mode 100644 lib/presentation/todo_section/ancillary_order_payment_page.dart create mode 100644 lib/presentation/todo_section/ancillary_procedures_details_page.dart create mode 100644 lib/presentation/todo_section/todo_page.dart create mode 100644 lib/presentation/todo_section/widgets/ancillary_orders_list.dart create mode 100644 lib/presentation/todo_section/widgets/ancillary_procedures_list.dart diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 029dbcb..77712e4 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -88,16 +88,16 @@ class ApiClientImp implements ApiClient { @override post( - String endPoint, { - required Map body, - required Function(dynamic response, int statusCode, {int? messageStatus, String? errorMessage}) onSuccess, - required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure, - bool isAllowAny = false, - bool isExternal = false, - bool isRCService = false, - bool isPaymentServices = false, - bool bypassConnectionCheck = true, - }) async { + String endPoint, { + required Map body, + required Function(dynamic response, int statusCode, {int? messageStatus, String? errorMessage}) onSuccess, + required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure, + bool isAllowAny = false, + bool isExternal = false, + bool isRCService = false, + bool isPaymentServices = false, + bool bypassConnectionCheck = true, + }) async { String url; if (isExternal) { url = endPoint; @@ -119,7 +119,8 @@ class ApiClientImp implements ApiClient { } else {} if (body.containsKey('isDentalAllowedBackend')) { - body['isDentalAllowedBackend'] = body.containsKey('isDentalAllowedBackend') ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND : IS_DENTAL_ALLOWED_BACKEND; + body['isDentalAllowedBackend'] = + body.containsKey('isDentalAllowedBackend') ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND : IS_DENTAL_ALLOWED_BACKEND; } if (!body.containsKey('IsPublicRequest')) { @@ -136,9 +137,9 @@ class ApiClientImp implements ApiClient { body['PatientType'] = PATIENT_TYPE_ID.toString(); } - // TODO : These should be from the appState if (user != null) { body['TokenID'] = body['TokenID'] ?? token; + body['PatientID'] = body['PatientID'] ?? user.patientId; body['PatientOutSA'] = body.containsKey('PatientOutSA') ? body['PatientOutSA'] ?? user.outSa : user.outSa; @@ -174,7 +175,7 @@ class ApiClientImp implements ApiClient { } // body['TokenID'] = "@dm!n"; - // body['PatientID'] = 3111528; + // body['PatientID'] = 4772429; // body['PatientTypeID'] = 1; // // body['PatientOutSA'] = 0; @@ -182,9 +183,10 @@ class ApiClientImp implements ApiClient { } body.removeWhere((key, value) => value == null); - log("body: ${json.encode(body)}"); log("uri: ${Uri.parse(url.trim())}"); + log("body: ${json.encode(body)}"); + final bool networkStatus = await Utils.checkConnection(bypassConnectionCheck: bypassConnectionCheck); if (!networkStatus) { @@ -210,35 +212,43 @@ class ApiClientImp implements ApiClient { onSuccess(parsed, statusCode, messageStatus: 1, errorMessage: ""); } else { onSuccess(parsed, statusCode, - messageStatus: parsed.contains('MessageStatus') ? parsed['MessageStatus'] : 1, errorMessage: parsed.contains('ErrorEndUserMessage') ? parsed['ErrorEndUserMessage'] : ""); + messageStatus: parsed.contains('MessageStatus') ? parsed['MessageStatus'] : 1, + errorMessage: parsed.contains('ErrorEndUserMessage') ? parsed['ErrorEndUserMessage'] : ""); } } else { if (parsed['Response_Message'] != null) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, + messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else { if (parsed['ErrorType'] == 4) { //TODO : handle app update - onFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, failureType: AppUpdateFailure("parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']")); + onFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, + failureType: AppUpdateFailure("parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']")); logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); } if (parsed['ErrorType'] == 2) { - // todo: handle Logout + // todo_section: handle Logout onFailure( parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, - failureType: UnAuthenticatedUserFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'] ?? "User is not Authenticated", url: url), + failureType: + UnAuthenticatedUserFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'] ?? "User is not Authenticated", url: url), ); // logApiEndpointError(endPoint, "session logged out", statusCode); } if (isAllowAny) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, + messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else if (parsed['IsAuthenticated'] == null) { if (parsed['isSMSSent'] == true) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, + messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else if (parsed['MessageStatus'] == 1) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, + messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else if (parsed['Result'] == 'OK') { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, + messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else { onFailure( parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], @@ -248,16 +258,19 @@ class ApiClientImp implements ApiClient { logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); } } else if (parsed['MessageStatus'] == 1 || parsed['SMSLoginRequired'] == true) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, + messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else if (parsed['IsAuthenticated'] == false) { onFailure( "User is not Authenticated", statusCode, - failureType: UnAuthenticatedUserFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'] ?? "User is not Authenticated", url: url), + failureType: + UnAuthenticatedUserFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'] ?? "User is not Authenticated", url: url), ); } else if (parsed['MessageStatus'] == 2 && parsed['IsAuthenticated']) { if (parsed['SameClinicApptList'] != null) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, + messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else { if (parsed['message'] == null && parsed['ErrorEndUserMessage'] == null) { if (parsed['ErrorSearchMsg'] == null) { @@ -276,7 +289,6 @@ class ApiClientImp implements ApiClient { logApiEndpointError(endPoint, parsed['ErrorSearchMsg'], statusCode); } } else { - onFailure( parsed['message'] ?? parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, @@ -287,7 +299,8 @@ class ApiClientImp implements ApiClient { } } else { if (parsed['SameClinicApptList'] != null) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, + messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else { if (parsed['message'] != null) { onFailure( diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 41ef922..5aee16b 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -1,10 +1,6 @@ import 'package:amazon_payfort/amazon_payfort.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; -var MAX_SMALL_SCREEN = 660; -final OPENTOK_API_KEY = '46209962'; -// final OPENTOK_API_KEY = '47464241'; - // PACKAGES and OFFERS var EXA_CART_API_BASE_URL = 'https://mdlaboratories.com/offersdiscounts'; // var EXA_CART_API_BASE_URL = 'http://10.200.101.75:9000'; @@ -265,7 +261,6 @@ var CANCEL_APPOINTMENT = "Services/Doctors.svc/REST/CancelAppointment"; var GENERATE_QR_APPOINTMENT = "Services/Doctors.svc/REST/GenerateQRAppointmentNo"; //URL send email appointment QR -var EMAIL_QR_APPOINTMENT = "Services/Notifications.svc/REST/sendEmailForOnLineCheckin"; //URL check payment status var CHECK_PAYMENT_STATUS = "Services/PayFort_Serv.svc/REST/GetRequestStatusByRequestID"; @@ -275,14 +270,8 @@ var CREATE_ADVANCE_PAYMENT = "Services/Doctors.svc/REST/CreateAdvancePayment"; var HIS_CREATE_ADVANCE_PAYMENT = "Services/Patients.svc/REST/HIS_CreateAdvancePayment"; -var ER_CREATE_ADVANCE_PAYMENT = "services/Doctors.svc/REST/ER_CreateAdvancePaymentForClinic"; - -var ER_INSERT_ADVANCE_PAYMENT = "services/Doctors.svc/REST/ER_InsertEROnlinePaymentDetails"; - var ADD_ADVANCE_NUMBER_REQUEST = 'Services/PayFort_Serv.svc/REST/AddAdvancedNumberRequest'; -var GENERATE_ANCILLARY_ORDERS_INVOICE = 'Services/Doctors.svc/REST/AutoGenerateAncillaryOrderInvoice'; - var IS_ALLOW_ASK_DOCTOR = 'Services/Doctors.svc/REST/GetPatientDoctorAppointmentResult'; var GET_CALL_REQUEST_TYPE = 'Services/Doctors.svc/REST/GetCallRequestType_LOV'; var ADD_VIDA_REQUEST = 'Services/ER_VirtualCall.svc/REST/PatientER_VidaRequestInseart'; @@ -308,8 +297,6 @@ var GET_LIVECARE_CLINIC_TIMING = 'Services/ER_VirtualCall.svc/REST/PatientER_Get var GET_ER_APPOINTMENT_FEES = 'Services/DoctorApplication.svc/REST/GetERAppointmentFees'; var GET_ER_APPOINTMENT_TIME = 'Services/ER_VirtualCall.svc/REST/GetRestTime'; -var CHECK_PATIENT_DERMA_PACKAGE = 'Services/OUTPs.svc/REST/getPatientPackageComponentsForOnlineCheckIn'; - var ADD_NEW_CALL_FOR_PATIENT_ER = 'Services/DoctorApplication.svc/REST/NewCallForPatientER'; var GET_LIVECARE_HISTORY = 'Services/ER_VirtualCall.svc/REST/GetPatientErVirtualHistory'; @@ -738,10 +725,6 @@ class ApiConsts { static String RCBaseUrl = 'https://rc.hmg.com/'; // RC API URL PROD - static String SELECT_DEVICE_IMEI = 'Services/Patients.svc/REST/Patient_SELECTDeviceIMEIbyIMEI'; - - static num VERSION_ID = 18.9; - static var payFortEnvironment = FortEnvironment.production; static var applePayMerchantId = "merchant.com.hmgwebservices"; @@ -849,7 +832,19 @@ class ApiConsts { static final String removeFileFromFamilyMembers = 'Services/Authentication.svc/REST/ActiveDeactive_PatientFile'; static final String acceptAndRejectFamilyFile = 'Services/Authentication.svc/REST/Update_FileStatus'; - // static values for Api + // Ancillary Order Apis + static final String getOnlineAncillaryOrderList = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderList'; + static final String getOnlineAncillaryOrderProcList = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderProcList'; + static final String generateAncillaryOrderInvoice = 'Services/Doctors.svc/REST/AutoGenerateAncillaryOrderInvoice'; + static final String autoGenerateAncillaryOrdersInvoice = 'Services/Doctors.svc/REST/AutoGenerateAncillaryOrderInvoice'; + static final String getRequestStatusByRequestID = 'Services/PayFort_Serv.svc/REST/GetRequestStatusByRequestID'; + + //Payment APIs + static final String applePayInsertRequest = "Services/PayFort_Serv.svc/REST/PayFort_ApplePayRequestData_Insert"; + static final String createAdvancePayments = 'Services/Patients.svc/REST/HIS_CreateAdvancePayment'; + static final String addAdvanceNumberRequest = 'Services/PayFort_Serv.svc/REST/AddAdvancedNumberRequest'; + + // ************ static values for Api **************** static final double appVersionID = 18.7; static final int appChannelId = 3; static final String appIpAddress = "10.20.10.20"; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index a82a9ad..a40518d 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -31,6 +31,8 @@ import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_mo import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; +import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart'; +import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; @@ -40,7 +42,6 @@ import 'package:hmg_patient_app_new/services/localauth_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; -import 'package:http/http.dart'; import 'package:local_auth/local_auth.dart'; import 'package:logger/web.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -98,11 +99,13 @@ class AppDependencies { getIt.registerLazySingleton(() => PrescriptionsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => InsuranceRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => PayfortRepoImp(loggerService: getIt(), apiClient: getIt())); - getIt.registerLazySingleton(() => LocalAuthService(loggerService: getIt(), localAuth: getIt())); + getIt.registerLazySingleton( + () => LocalAuthService(loggerService: getIt(), localAuth: getIt())); getIt.registerLazySingleton(() => HabibWalletRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => MedicalFileRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => ImmediateLiveCareRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => EmergencyServicesRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => TodoSectionRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -158,7 +161,13 @@ class AppDependencies { ); getIt.registerLazySingleton( - () => BookAppointmentsViewModel(bookAppointmentsRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt(), myAppointmentsViewModel: getIt(), locationUtils: getIt(), dialogService: getIt()), + () => BookAppointmentsViewModel( + bookAppointmentsRepo: getIt(), + errorHandlerService: getIt(), + navigationService: getIt(), + myAppointmentsViewModel: getIt(), + locationUtils: getIt(), + dialogService: getIt()), ); getIt.registerLazySingleton( @@ -172,7 +181,13 @@ class AppDependencies { getIt.registerLazySingleton( () => AuthenticationViewModel( - authenticationRepo: getIt(), cacheService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt(), errorHandlerService: getIt(), localAuthService: getIt()), + authenticationRepo: getIt(), + cacheService: getIt(), + navigationService: getIt(), + dialogService: getIt(), + appState: getIt(), + errorHandlerService: getIt(), + localAuthService: getIt()), ); getIt.registerLazySingleton(() => ProfileSettingsViewModel()); @@ -185,8 +200,7 @@ class AppDependencies { ); getIt.registerLazySingleton( - () => - AppointmentViaRegionViewmodel( + () => AppointmentViaRegionViewmodel( navigationService: getIt(), appState: getIt(), ), @@ -202,6 +216,13 @@ class AppDependencies { ), ); + getIt.registerLazySingleton( + () => TodoSectionViewModel( + todoSectionRepo: getIt(), + errorHandlerService: getIt(), + ), + ); + // Screen-specific VMs → Factory // getIt.registerFactory( // () => BookAppointmentsViewModel( diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index a4ea936..e57039c 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:easy_localization/easy_localization.dart'; import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; @@ -96,6 +98,7 @@ class RequestUtils { request.patientIdentificationID = request.nationalID = (registeredData.patientIdentificationId ?? 0); request.dob = registeredData.dob; request.isRegister = registeredData.isRegister; + log("nationIdText: ${nationIdText}"); } else { if (fileNo) { request.patientID = patientId ?? int.parse(nationIdText); @@ -199,7 +202,8 @@ class RequestUtils { return request; } - static dynamic getUserSignupCompletionRequest({String? fullName, String? emailAddress, GenderTypeEnum? gender, MaritalStatusTypeEnum? maritalStatus}) { + static dynamic getUserSignupCompletionRequest( + {String? fullName, String? emailAddress, GenderTypeEnum? gender, MaritalStatusTypeEnum? maritalStatus}) { AppState appState = getIt.get(); bool isDubai = appState.getUserRegistrationPayload.patientOutSa == 1 ? true : false; @@ -215,11 +219,19 @@ class RequestUtils { return { "Patientobject": { "TempValue": true, - "PatientIdentificationType": - (isDubai ? appState.getUserRegistrationPayload.patientIdentificationId?.toString().substring(0, 1) : appState.getNHICUserData.idNumber!.substring(0, 1)) == "1" ? 1 : 2, - "PatientIdentificationNo": isDubai ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : appState.getNHICUserData.idNumber.toString(), + "PatientIdentificationType": (isDubai + ? appState.getUserRegistrationPayload.patientIdentificationId?.toString().substring(0, 1) + : appState.getNHICUserData.idNumber!.substring(0, 1)) == + "1" + ? 1 + : 2, + "PatientIdentificationNo": + isDubai ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : appState.getNHICUserData.idNumber.toString(), "MobileNumber": appState.getUserRegistrationPayload.patientMobileNumber ?? 0, - "PatientOutSA": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || appState.getUserRegistrationPayload.zipCode == '+966') ? 0 : 1, + "PatientOutSA": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || + appState.getUserRegistrationPayload.zipCode == '+966') + ? 0 + : 1, "FirstNameN": isDubai ? "..." : appState.getNHICUserData.firstNameAr, "FirstName": isDubai ? (names.isNotEmpty ? names[0] : "...") : appState.getNHICUserData.firstNameEn, "MiddleNameN": isDubai ? "..." : appState.getNHICUserData.secondNameAr, @@ -233,7 +245,10 @@ class RequestUtils { "eHealthIDField": isDubai ? null : appState.getNHICUserData.healthId, "DateofBirthN": date, "EmailAddress": emailAddress, - "SourceType": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || appState.getUserRegistrationPayload.zipCode == '+966') ? "1" : "2", + "SourceType": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || + appState.getUserRegistrationPayload.zipCode == '+966') + ? "1" + : "2", "PreferredLanguage": appState.getLanguageCode() == "ar" ? (isDubai ? "1" : 1) : (isDubai ? "2" : 2), "Marital": isDubai ? (maritalStatus == MaritalStatusTypeEnum.single @@ -247,20 +262,25 @@ class RequestUtils { ? '1' : '2'), }, - "PatientIdentificationID": isDubai ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : appState.getNHICUserData.idNumber.toString(), + "PatientIdentificationID": + isDubai ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : appState.getNHICUserData.idNumber.toString(), "PatientMobileNumber": appState.getUserRegistrationPayload.patientMobileNumber.toString()[0] == '0' ? appState.getUserRegistrationPayload.patientMobileNumber : '0${appState.getUserRegistrationPayload.patientMobileNumber}', "DOB": dob, "IsHijri": appState.getUserRegistrationPayload.isHijri, - "PatientOutSA": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || appState.getUserRegistrationPayload.zipCode == '+966') ? 0 : 1, + "PatientOutSA": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || + appState.getUserRegistrationPayload.zipCode == '+966') + ? 0 + : 1, "isDentalAllowedBackend": appState.getUserRegistrationPayload.isDentalAllowedBackend, "ZipCode": appState.getUserRegistrationPayload.zipCode, if (!isDubai) "HealthId": appState.getNHICUserData.healthId, }; } - static Future getAddFamilyRequest({required String nationalIDorFile, required String mobileNo, required String countryCode}) async { + static Future getAddFamilyRequest( + {required String nationalIDorFile, required String mobileNo, required String countryCode}) async { FamilyFileRequest request = FamilyFileRequest(); int? loginType = 0; diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 491aa49..e3bd975 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -315,7 +315,8 @@ class Utils { crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox(height: isSmallWidget ? 0.h : 48.h), - Lottie.asset(AppAnimations.noData, repeat: false, reverse: false, frameRate: FrameRate(60), width: width.h, height: height.h, fit: BoxFit.fill), + Lottie.asset(AppAnimations.noData, + repeat: false, reverse: false, frameRate: FrameRate(60), width: width.h, height: height.h, fit: BoxFit.fill), SizedBox(height: 16.h), (noDataText ?? LocaleKeys.noDataAvailable.tr()) .toText16(weight: FontWeight.w500, color: AppColors.greyTextColor, isCenter: true) @@ -331,7 +332,8 @@ class Utils { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Lottie.asset(AppAnimations.loadingAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), + Lottie.asset(AppAnimations.loadingAnimation, + repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), SizedBox(height: 8.h), (loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor, isCenter: true), SizedBox(height: 8.h), @@ -357,7 +359,8 @@ class Utils { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Lottie.asset(AppAnimations.errorAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), + Lottie.asset(AppAnimations.errorAnimation, + repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), SizedBox(height: 8.h), (loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor), SizedBox(height: 8.h), @@ -365,12 +368,14 @@ class Utils { ).center; } - static Widget getWarningWidget({String? loadingText, bool isShowActionButtons = false, Widget? bodyWidget, Function? onConfirmTap, Function? onCancelTap}) { + static Widget getWarningWidget( + {String? loadingText, bool isShowActionButtons = false, Widget? bodyWidget, Function? onConfirmTap, Function? onCancelTap}) { return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Lottie.asset(AppAnimations.warningAnimation, repeat: false, reverse: false, frameRate: FrameRate(60), width: 128.h, height: 128.h, fit: BoxFit.fill), + Lottie.asset(AppAnimations.warningAnimation, + repeat: false, reverse: false, frameRate: FrameRate(60), width: 128.h, height: 128.h, fit: BoxFit.fill), SizedBox(height: 8.h), (loadingText ?? LocaleKeys.loadingText.tr()).toText14(color: AppColors.blackColor, letterSpacing: 0), SizedBox(height: 16.h), @@ -690,7 +695,7 @@ class Utils { fit: fit, errorBuilder: errorBuilder ?? (_, __, ___) { - //todo change the error builder icon that it is returning + //todo_section change the error builder icon that it is returning return Utils.buildSvgWithAssets(width: iconW, height: iconH, icon: AppAssets.no_visit_icon); }, ); @@ -799,7 +804,7 @@ class Utils { static Future createFileFromString(String encodedStr, String ext) async { Uint8List bytes = base64.decode(encodedStr); String dir = (await getApplicationDocumentsDirectory()).path; - File file = File("$dir/" + DateTime.now().millisecondsSinceEpoch.toString() + "." + ext); + File file = File("$dir/${DateTime.now().millisecondsSinceEpoch}.$ext"); await file.writeAsBytes(bytes); return file.path; } diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 8be2b8e..d95b316 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -338,7 +338,7 @@ class AuthenticationViewModel extends ChangeNotifier { _navigationService.pop(); }); }, - activationCode: null, //todo silent login case halded on the repo itself.. + activationCode: null, //todo_section silent login case halded on the repo itself.. ); } } diff --git a/lib/features/emergency_services/emergency_services_view_model.dart b/lib/features/emergency_services/emergency_services_view_model.dart index 77e5823..f769528 100644 --- a/lib/features/emergency_services/emergency_services_view_model.dart +++ b/lib/features/emergency_services/emergency_services_view_model.dart @@ -154,7 +154,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { } handleGMSMapCameraMoved(GMSMapServices.CameraPosition value) { - //todo handle the camera moved position for GMS devices + //todo_section handle the camera moved position for GMS devices } HMSCameraServices.CameraPosition getHMSLocation() { @@ -164,7 +164,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { } handleHMSMapCameraMoved(HMSCameraServices.CameraPosition value) { - //todo handle the camera moved position for HMS devices + //todo_section handle the camera moved position for HMS devices } void navigateTOAmbulancePage() { diff --git a/lib/features/my_appointments/appointment_via_region_viewmodel.dart b/lib/features/my_appointments/appointment_via_region_viewmodel.dart index 6c6354a..4a0ffab 100644 --- a/lib/features/my_appointments/appointment_via_region_viewmodel.dart +++ b/lib/features/my_appointments/appointment_via_region_viewmodel.dart @@ -122,7 +122,7 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier { } void handleLastStepForDentalAndLaser() { - //todo handle the routing here + //todo_section handle the routing here navigationService.pop(); navigationService.push( CustomPageRoute( diff --git a/lib/features/payfort/payfort_view_model.dart b/lib/features/payfort/payfort_view_model.dart index 89effcd..d4c13ab 100644 --- a/lib/features/payfort/payfort_view_model.dart +++ b/lib/features/payfort/payfort_view_model.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:amazon_payfort/amazon_payfort.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; @@ -36,7 +38,8 @@ class PayfortViewModel extends ChangeNotifier { notifyListeners(); } - Future getPayfortConfigurations({int? serviceId, int? projectId, int integrationId = 2, Function(dynamic)? onSuccess, Function(String)? onError}) async { + Future getPayfortConfigurations( + {int? serviceId, int? projectId, int integrationId = 2, Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await payfortRepo.getPayfortConfigurations(serviceId: serviceId, projectId: projectId, integrationId: integrationId); result.fold( @@ -56,7 +59,8 @@ class PayfortViewModel extends ChangeNotifier { ); } - Future applePayRequestInsert({required ApplePayInsertRequest applePayInsertRequest, Function(dynamic)? onSuccess, Function(String)? onError}) async { + Future applePayRequestInsert( + {required ApplePayInsertRequest applePayInsertRequest, Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await payfortRepo.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest); result.fold( @@ -102,7 +106,7 @@ class PayfortViewModel extends ChangeNotifier { onError!(failure.message); }, (apiResponse) { - print(apiResponse.data); + log(apiResponse.data); if (onSuccess != null) { onSuccess(apiResponse); } @@ -112,15 +116,21 @@ class PayfortViewModel extends ChangeNotifier { } Future updateTamaraRequestStatus( - {required String responseMessage, required String status, required String clientRequestID, required String tamaraOrderID, Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await payfortRepo.updateTamaraRequestStatus(responseMessage: responseMessage, status: status, clientRequestID: clientRequestID, tamaraOrderID: tamaraOrderID); + {required String responseMessage, + required String status, + required String clientRequestID, + required String tamaraOrderID, + Function(dynamic)? onSuccess, + Function(String)? onError}) async { + final result = await payfortRepo.updateTamaraRequestStatus( + responseMessage: responseMessage, status: status, clientRequestID: clientRequestID, tamaraOrderID: tamaraOrderID); result.fold( (failure) async { onError!(failure.message); }, (apiResponse) { - print(apiResponse.data); + log(apiResponse.data); if (onSuccess != null) { onSuccess(apiResponse); } @@ -134,7 +144,7 @@ class PayfortViewModel extends ChangeNotifier { String? applePayShaType, String? applePayShaRequestPhrase, }) async { - var sdkTokenResponse; + SdkTokenResponse? sdkTokenResponse; try { String? deviceId = await _payfort.getDeviceId(); @@ -168,7 +178,7 @@ class PayfortViewModel extends ChangeNotifier { }, ); } catch (e) { - print("Error here: ${e.toString()}"); + log("Error here: ${e.toString()}"); } return sdkTokenResponse; } @@ -234,7 +244,8 @@ class PayfortViewModel extends ChangeNotifier { } } - Future markAppointmentAsTamaraPaid({required int projectID, required int appointmentNo, Function(dynamic)? onSuccess, Function(String)? onError}) async { + Future markAppointmentAsTamaraPaid( + {required int projectID, required int appointmentNo, Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await payfortRepo.markAppointmentAsTamaraPaid(projectID: projectID, appointmentNo: appointmentNo); result.fold( @@ -242,7 +253,7 @@ class PayfortViewModel extends ChangeNotifier { onError!(failure.message); }, (apiResponse) { - print(apiResponse.data); + log(apiResponse.data); if (onSuccess != null) { onSuccess(apiResponse); } diff --git a/lib/features/radiology/radiology_repo.dart b/lib/features/radiology/radiology_repo.dart index 0a44428..b81fd50 100644 --- a/lib/features/radiology/radiology_repo.dart +++ b/lib/features/radiology/radiology_repo.dart @@ -9,11 +9,12 @@ import 'package:hmg_patient_app_new/features/radiology/models/resp_models/patien import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class RadiologyRepo { - Future>>> getPatientRadiologyOrders({required String patientId}); + Future>>> getPatientRadiologyOrders(); Future>> getRadiologyImage({required PatientRadiologyResponseModel patientRadiologyResponseModel}); - Future>> getRadiologyReportPDF({required PatientRadiologyResponseModel patientRadiologyResponseModel, required AuthenticatedUser authenticatedUser}); + Future>> getRadiologyReportPDF( + {required PatientRadiologyResponseModel patientRadiologyResponseModel, required AuthenticatedUser authenticatedUser}); } class RadiologyRepoImp implements RadiologyRepo { @@ -23,7 +24,7 @@ class RadiologyRepoImp implements RadiologyRepo { RadiologyRepoImp({required this.loggerService, required this.apiClient}); @override - Future>>> getPatientRadiologyOrders({required String patientId}) async { + Future>>> getPatientRadiologyOrders() async { Map mapDevice = {}; try { @@ -40,10 +41,16 @@ class RadiologyRepoImp implements RadiologyRepo { try { if (response['FinalRadiologyList'] != null && response['FinalRadiologyList'].length != 0) { final list = response['FinalRadiologyList']; - radOrders = list.map((item) => PatientRadiologyResponseModel.fromJson(item as Map)).toList().cast(); + radOrders = list + .map((item) => PatientRadiologyResponseModel.fromJson(item as Map)) + .toList() + .cast(); } else { final list = response['FinalRadiologyListAPI']; - radOrders = list.map((item) => PatientRadiologyResponseModel.fromJson(item as Map)).toList().cast(); + radOrders = list + .map((item) => PatientRadiologyResponseModel.fromJson(item as Map)) + .toList() + .cast(); } apiResponse = GenericApiModel>( @@ -107,7 +114,8 @@ class RadiologyRepoImp implements RadiologyRepo { } @override - Future>> getRadiologyReportPDF({required PatientRadiologyResponseModel patientRadiologyResponseModel, required AuthenticatedUser authenticatedUser}) async { + Future>> getRadiologyReportPDF( + {required PatientRadiologyResponseModel patientRadiologyResponseModel, required AuthenticatedUser authenticatedUser}) async { Map mapDevice = { "InvoiceNo": Utils.isVidaPlusProject(patientRadiologyResponseModel.projectID!) ? 0 : patientRadiologyResponseModel.invoiceNo, "InvoiceNo_VP": Utils.isVidaPlusProject(patientRadiologyResponseModel.projectID!) ? patientRadiologyResponseModel.invoiceNo : 0, @@ -121,7 +129,8 @@ class RadiologyRepoImp implements RadiologyRepo { 'ClinicName': patientRadiologyResponseModel.clinicDescription, 'DateofBirth': authenticatedUser.dateofBirth, 'DoctorName': patientRadiologyResponseModel.doctorName, - 'OrderDate': '${patientRadiologyResponseModel.orderDate!.year}-${patientRadiologyResponseModel.orderDate!.month}-${patientRadiologyResponseModel.orderDate!.day}', + 'OrderDate': + '${patientRadiologyResponseModel.orderDate!.year}-${patientRadiologyResponseModel.orderDate!.month}-${patientRadiologyResponseModel.orderDate!.day}', 'PatientIditificationNum': authenticatedUser.patientIdentificationNo, 'PatientMobileNumber': authenticatedUser.mobileNumber, 'PatientName': "${authenticatedUser.firstName!} ${authenticatedUser.lastName!}", diff --git a/lib/features/radiology/radiology_view_model.dart b/lib/features/radiology/radiology_view_model.dart index 3441881..de6a796 100644 --- a/lib/features/radiology/radiology_view_model.dart +++ b/lib/features/radiology/radiology_view_model.dart @@ -19,7 +19,7 @@ class RadiologyViewModel extends ChangeNotifier { RadiologyViewModel({required this.radiologyRepo, required this.errorHandlerService}); - initRadiologyProvider() { + initRadiologyViewModel() { patientRadiologyOrders.clear(); isRadiologyOrdersLoading = true; isRadiologyPDFReportLoading = true; @@ -29,7 +29,7 @@ class RadiologyViewModel extends ChangeNotifier { } Future getPatientRadiologyOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await radiologyRepo.getPatientRadiologyOrders(patientId: "1231755"); + final result = await radiologyRepo.getPatientRadiologyOrders(); result.fold( (failure) async => await errorHandlerService.handleError(failure: failure), @@ -48,7 +48,8 @@ class RadiologyViewModel extends ChangeNotifier { ); } - Future getRadiologyImage({required PatientRadiologyResponseModel patientRadiologyResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { + Future getRadiologyImage( + {required PatientRadiologyResponseModel patientRadiologyResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await radiologyRepo.getRadiologyImage(patientRadiologyResponseModel: patientRadiologyResponseModel); result.fold( @@ -68,8 +69,12 @@ class RadiologyViewModel extends ChangeNotifier { } Future getRadiologyPDF( - {required PatientRadiologyResponseModel patientRadiologyResponseModel, required AuthenticatedUser authenticatedUser, Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await radiologyRepo.getRadiologyReportPDF(patientRadiologyResponseModel: patientRadiologyResponseModel, authenticatedUser: authenticatedUser); + {required PatientRadiologyResponseModel patientRadiologyResponseModel, + required AuthenticatedUser authenticatedUser, + Function(dynamic)? onSuccess, + Function(String)? onError}) async { + final result = + await radiologyRepo.getRadiologyReportPDF(patientRadiologyResponseModel: patientRadiologyResponseModel, authenticatedUser: authenticatedUser); result.fold( (failure) async => await errorHandlerService.handleError( diff --git a/lib/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart b/lib/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart new file mode 100644 index 0000000..bad8006 --- /dev/null +++ b/lib/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart @@ -0,0 +1,109 @@ +// Dart model for the "AncillaryOrderList" structure +// Uses DateUtil.convertStringToDate and DateUtil.dateToDotNetString from your project to parse/serialize .NET-style dates. +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; + +class AncillaryOrderListModel { + List? ancillaryOrderList; + + AncillaryOrderListModel({this.ancillaryOrderList}); + + factory AncillaryOrderListModel.fromJson(Map json) => AncillaryOrderListModel( + ancillaryOrderList: json['AncillaryOrderList'] != null + ? List.from( + (json['AncillaryOrderList'] as List).map( + (x) => AncillaryOrderGroup.fromJson(x as Map), + ), + ) + : null, + ); +} + +class AncillaryOrderGroup { + List? ancillaryOrderList; + dynamic errCode; + String? message; + int? patientID; + String? patientName; + int? patientType; + int? projectID; + String? projectName; + String? setupID; + int? statusCode; + + AncillaryOrderGroup({ + this.ancillaryOrderList, + this.errCode, + this.message, + this.patientID, + this.patientName, + this.patientType, + this.projectID, + this.projectName, + this.setupID, + this.statusCode, + }); + + factory AncillaryOrderGroup.fromJson(Map json) => AncillaryOrderGroup( + ancillaryOrderList: json['AncillaryOrderList'] != null + ? List.from( + (json['AncillaryOrderList'] as List).map( + (x) => AncillaryOrderItem.fromJson(x as Map), + ), + ) + : null, + errCode: json['ErrCode'], + message: json['Message'] as String?, + patientID: json['PatientID'] as int?, + patientName: json['PatientName'] as String?, + patientType: json['PatientType'] as int?, + projectID: json['ProjectID'] as int?, + projectName: json['ProjectName'] as String?, + setupID: json['SetupID'] as String?, + statusCode: json['StatusCode'] as int?, + ); +} + +class AncillaryOrderItem { + dynamic ancillaryProcedureListModels; + DateTime? appointmentDate; + int? appointmentNo; + int? clinicID; + String? clinicName; + int? doctorID; + String? doctorName; + int? invoiceNo; + bool? isCheckInAllow; + bool? isQueued; + DateTime? orderDate; + int? orderNo; + + AncillaryOrderItem({ + this.ancillaryProcedureListModels, + this.appointmentDate, + this.appointmentNo, + this.clinicID, + this.clinicName, + this.doctorID, + this.doctorName, + this.invoiceNo, + this.isCheckInAllow, + this.isQueued, + this.orderDate, + this.orderNo, + }); + + factory AncillaryOrderItem.fromJson(Map json) => AncillaryOrderItem( + ancillaryProcedureListModels: json['AncillaryProcedureListModels'], + appointmentDate: DateUtil.convertStringToDate(json['AppointmentDate']), + appointmentNo: json['AppointmentNo'] as int?, + clinicID: json['ClinicID'] as int?, + clinicName: json['ClinicName'] as String?, + doctorID: json['DoctorID'] as int?, + doctorName: json['DoctorName'] as String?, + invoiceNo: json['Invoiceno'] as int?, + isCheckInAllow: json['IsCheckInAllow'] as bool?, + isQueued: json['IsQueued'] as bool?, + orderDate: DateUtil.convertStringToDate(json['OrderDate']), + orderNo: json['OrderNo'] as int?, + ); +} diff --git a/lib/features/todo_section/models/resp_models/ancillary_order_procedures_detail_response_model.dart b/lib/features/todo_section/models/resp_models/ancillary_order_procedures_detail_response_model.dart new file mode 100644 index 0000000..26abde3 --- /dev/null +++ b/lib/features/todo_section/models/resp_models/ancillary_order_procedures_detail_response_model.dart @@ -0,0 +1,221 @@ +// Dart model classes for "AncillaryOrderProcList" +// Generated for user: faizatflutter +// Uses DateUtil.convertStringToDate for parsing .NET-style dates (same approach as your PatientRadiologyResponseModel) + +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; + +class AncillaryOrderProcListModel { + List? ancillaryOrderProcList; + + AncillaryOrderProcListModel({this.ancillaryOrderProcList}); + + factory AncillaryOrderProcListModel.fromJson(Map json) => AncillaryOrderProcListModel( + ancillaryOrderProcList: json['AncillaryOrderProcList'] != null + ? List.from( + (json['AncillaryOrderProcList'] as List).map( + (x) => AncillaryOrderProcedureItem.fromJson(x as Map), + ), + ) + : null, + ); +} + +class AncillaryOrderProcedureItem { + List? ancillaryOrderProcDetailsList; + DateTime? appointmentDate; + int? appointmentNo; + int? clinicID; + String? clinicName; + int? companyID; + String? companyName; + int? doctorID; + String? doctorName; + dynamic errCode; + int? groupID; + String? insurancePolicyNo; + String? message; + String? patientCardID; + int? patientID; + String? patientName; + int? patientType; + int? policyID; + String? policyName; + int? projectID; + String? setupID; + int? statusCode; + int? subCategoryID; + String? subPolicyNo; + + AncillaryOrderProcedureItem({ + this.ancillaryOrderProcDetailsList, + this.appointmentDate, + this.appointmentNo, + this.clinicID, + this.clinicName, + this.companyID, + this.companyName, + this.doctorID, + this.doctorName, + this.errCode, + this.groupID, + this.insurancePolicyNo, + this.message, + this.patientCardID, + this.patientID, + this.patientName, + this.patientType, + this.policyID, + this.policyName, + this.projectID, + this.setupID, + this.statusCode, + this.subCategoryID, + this.subPolicyNo, + }); + + factory AncillaryOrderProcedureItem.fromJson(Map json) => AncillaryOrderProcedureItem( + ancillaryOrderProcDetailsList: json['AncillaryOrderProcDetailsList'] != null + ? List.from( + (json['AncillaryOrderProcDetailsList'] as List).map( + (x) => AncillaryOrderProcDetail.fromJson(x as Map), + ), + ) + : null, + appointmentDate: DateUtil.convertStringToDate(json['AppointmentDate']), + appointmentNo: json['AppointmentNo'] as int?, + clinicID: json['ClinicID'] as int?, + clinicName: json['ClinicName'] as String?, + companyID: json['CompanyID'] as int?, + companyName: json['CompanyName'] as String?, + doctorID: json['DoctorID'] as int?, + doctorName: json['DoctorName'] as String?, + errCode: json['ErrCode'], + groupID: json['GroupID'] as int?, + insurancePolicyNo: json['InsurancePolicyNo'] as String?, + message: json['Message'] as String?, + patientCardID: json['PatientCardID'] as String?, + patientID: json['PatientID'] as int?, + patientName: json['PatientName'] as String?, + patientType: json['PatientType'] as int?, + policyID: json['PolicyID'] as int?, + policyName: json['PolicyName'] as String?, + projectID: json['ProjectID'] as int?, + setupID: json['SetupID'] as String?, + statusCode: json['StatusCode'] as int?, + subCategoryID: json['SubCategoryID'] as int?, + subPolicyNo: json['SubPolicyNo'] as String?, + ); +} + +class AncillaryOrderProcDetail { + int? approvalLineItemNo; + int? approvalNo; + String? approvalStatus; + int? approvalStatusID; + num? companyShare; + num? companyShareWithTax; + num? companyTaxAmount; + num? discountAmount; + int? discountCategory; + String? discountType; + num? discountTypeValue; + bool? isApprovalCreated; + bool? isApprovalRequired; + dynamic isCheckInAllow; + bool? isCovered; + bool? isLab; + DateTime? orderDate; + int? orderLineItemNo; + int? orderNo; + int? partnerID; + num? partnerShare; + String? partnerShareType; + num? patientShare; + num? patientShareWithTax; + num? patientTaxAmount; + num? procPrice; + int? procedureCategoryID; + String? procedureCategoryName; + String? procedureID; + String? procedureName; + num? taxAmount; + num? taxPct; + + AncillaryOrderProcDetail({ + this.approvalLineItemNo, + this.approvalNo, + this.approvalStatus, + this.approvalStatusID, + this.companyShare, + this.companyShareWithTax, + this.companyTaxAmount, + this.discountAmount, + this.discountCategory, + this.discountType, + this.discountTypeValue, + this.isApprovalCreated, + this.isApprovalRequired, + this.isCheckInAllow, + this.isCovered, + this.isLab, + this.orderDate, + this.orderLineItemNo, + this.orderNo, + this.partnerID, + this.partnerShare, + this.partnerShareType, + this.patientShare, + this.patientShareWithTax, + this.patientTaxAmount, + this.procPrice, + this.procedureCategoryID, + this.procedureCategoryName, + this.procedureID, + this.procedureName, + this.taxAmount, + this.taxPct, + }); + + factory AncillaryOrderProcDetail.fromJson(Map json) => AncillaryOrderProcDetail( + approvalLineItemNo: json['ApprovalLineItemNo'] as int?, + approvalNo: json['ApprovalNo'] as int?, + approvalStatus: json['ApprovalStatus'] as String?, + approvalStatusID: json['ApprovalStatusID'] as int?, + companyShare: _toNum(json['CompanyShare']), + companyShareWithTax: _toNum(json['CompanyShareWithTax']), + companyTaxAmount: _toNum(json['CompanyTaxAmount']), + discountAmount: _toNum(json['DiscountAmount']), + discountCategory: json['DiscountCategory'] as int?, + discountType: json['DiscountType'] as String?, + discountTypeValue: _toNum(json['DiscountTypeValue']), + isApprovalCreated: json['IsApprovalCreated'] as bool?, + isApprovalRequired: json['IsApprovalRequired'] as bool?, + isCheckInAllow: json['IsCheckInAllow'], + isCovered: json['IsCovered'] as bool?, + isLab: json['IsLab'] as bool?, + orderDate: DateUtil.convertStringToDate(json['OrderDate']), + orderLineItemNo: json['OrderLineItemNo'] as int?, + orderNo: json['OrderNo'] as int?, + partnerID: json['PartnerID'] as int?, + partnerShare: _toNum(json['PartnerShare']), + partnerShareType: json['PartnerShareType'] as String?, + patientShare: _toNum(json['PatientShare']), + patientShareWithTax: _toNum(json['PatientShareWithTax']), + patientTaxAmount: _toNum(json['PatientTaxAmount']), + procPrice: _toNum(json['ProcPrice']), + procedureCategoryID: json['ProcedureCategoryID'] as int?, + procedureCategoryName: json['ProcedureCategoryName'] as String?, + procedureID: json['ProcedureID'] as String?, + procedureName: json['ProcedureName'] as String?, + taxAmount: _toNum(json['TaxAmount']), + taxPct: _toNum(json['TaxPct']), + ); +} + +// Helper to safely parse numeric fields that may be int/double/string/null +num? _toNum(dynamic v) { + if (v == null) return null; + if (v is num) return v; + if (v is String) return num.tryParse(v); + return null; +} diff --git a/lib/features/todo_section/todo_section_repo.dart b/lib/features/todo_section/todo_section_repo.dart new file mode 100644 index 0000000..60754e2 --- /dev/null +++ b/lib/features/todo_section/todo_section_repo.dart @@ -0,0 +1,377 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_procedures_detail_response_model.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class TodoSectionRepo { + Future>>> getOnlineAncillaryOrderList(); + + Future>>> getOnlineAncillaryOrderDetailsProceduresList({ + required int appointmentNoVida, + required int orderNo, + required int projectID, + }); + + Future> checkPaymentStatus({required String transID}); + + Future> createAdvancePayment({ + required int projectID, + required double paymentAmount, + required String paymentReference, + required String paymentMethodName, + required int patientTypeID, + required String patientName, + required int patientID, + required String setupID, + required bool isAncillaryOrder, + }); + + Future> addAdvancedNumberRequest({ + required String advanceNumber, + required String paymentReference, + required int appointmentID, + required int patientID, + required int patientTypeID, + required int patientOutSA, + }); + + Future> autoGenerateAncillaryOrdersInvoice({ + required int orderNo, + required int projectID, + required int appointmentNo, + required List selectedProcedures, + required int languageID, + }); + + Future> applePayInsertRequest({required dynamic applePayInsertRequest}); +} + +class TodoSectionRepoImp implements TodoSectionRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + TodoSectionRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future>>> getOnlineAncillaryOrderList() async { + Map mapDevice = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.getOnlineAncillaryOrderList, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + List ancillaryOrders = []; + + // Parse the nested structure + if (response['AncillaryOrderList'] != null && response['AncillaryOrderList'] is List) { + final groupsList = response['AncillaryOrderList'] as List; + + // Iterate through each group + for (var group in groupsList) { + if (group is Map && group['AncillaryOrderList'] != null) { + final ordersList = group['AncillaryOrderList'] as List; + + // Parse each order item in the group + for (var orderJson in ordersList) { + if (orderJson is Map) { + ancillaryOrders.add(AncillaryOrderItem.fromJson(orderJson)); + } + } + } + } + } + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: ancillaryOrders, + ); + } catch (e) { + loggerService.logInfo("Error parsing ancillary orders: ${e.toString()}"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + loggerService.logError("Unknown error in getOnlineAncillaryOrderList: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>>> getOnlineAncillaryOrderDetailsProceduresList({ + required int appointmentNoVida, + required int orderNo, + required int projectID, + }) async { + Map mapDevice = { + 'AppointmentNo_Vida': appointmentNoVida, + 'OrderNo': orderNo, + 'ProjectID': projectID, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.getOnlineAncillaryOrderProcList, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + List ancillaryOrdersProcedures = []; + + // Parse the flat array structure (NOT nested like AncillaryOrderList) + if (response['AncillaryOrderProcList'] != null && response['AncillaryOrderProcList'] is List) { + final procList = response['AncillaryOrderProcList'] as List; + + // Parse each procedure item directly + for (var procJson in procList) { + if (procJson is Map) { + ancillaryOrdersProcedures.add(AncillaryOrderProcedureItem.fromJson(procJson)); + } + } + } + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: ancillaryOrdersProcedures, + ); + } catch (e) { + loggerService.logError("Error parsing ancillary Procedures: ${e.toString()}"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + loggerService.logError("Unknown error in getOnlineAncillaryOrderDetailsProceduresList: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future> checkPaymentStatus({required String transID}) async { + Map mapDevice = {'ClientRequestID': transID}; + + try { + dynamic apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.getRequestStatusByRequestID, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + apiResponse = response; + }, + ); + if (failure != null) return Left(failure!); + return Right(apiResponse); + } catch (e) { + loggerService.logError("Unknown error in checkPaymentStatus: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future> createAdvancePayment({ + required int projectID, + required double paymentAmount, + required String paymentReference, + required String paymentMethodName, + required int patientTypeID, + required String patientName, + required int patientID, + required String setupID, + required bool isAncillaryOrder, + }) async { + // //VersionID (number) + // // Channel (number) + // // IPAdress (string) + // // generalid (string) + // // LanguageID (number) + // // Latitude (number) + // // Longitude (number) + // // DeviceTypeID (number) + // // PatientType (number) + // // PatientTypeID (number) + // // PatientID (number) + // // PatientOutSA (number) + // // TokenID (string) + // // SessionID (string) + + Map mapDevice = { + 'CustName': patientName, + 'CustID': patientID, + 'SetupID': setupID, + 'ProjectID': projectID, + 'AccountID': patientID, + 'PaymentAmount': paymentAmount, + 'NationalityID': null, + 'DepositorName': patientName, + 'CreatedBy': 3, + 'PaymentMethodName': paymentMethodName, + 'PaymentReference': paymentReference, + 'PaymentMethod': paymentMethodName, + 'IsAncillaryOrder': isAncillaryOrder, + }; + + try { + dynamic apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.createAdvancePayments, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + apiResponse = response; + }, + ); + if (failure != null) return Left(failure!); + return Right(apiResponse); + } catch (e) { + loggerService.logError("Unknown error in createAdvancePayment: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future> addAdvancedNumberRequest({ + required String advanceNumber, + required String paymentReference, + required int appointmentID, + required int patientID, + required int patientTypeID, + required int patientOutSA, + }) async { + Map mapDevice = { + 'AdvanceNumber': advanceNumber, + 'PaymentReference': paymentReference, + 'AppointmentID': appointmentID, + 'PatientID': patientID, + 'PatientTypeID': patientTypeID, + 'PatientOutSA': patientOutSA, + }; + + try { + dynamic apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.addAdvanceNumberRequest, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + apiResponse = response; + }, + ); + if (failure != null) return Left(failure!); + return Right(apiResponse); + } catch (e) { + loggerService.logError("Unknown error in addAdvancedNumberRequest: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future> autoGenerateAncillaryOrdersInvoice({ + required int orderNo, + required int projectID, + required int appointmentNo, + required List selectedProcedures, + required int languageID, + }) async { + // Extract procedure IDs from selectedProcedures + List procedureOrderIDs = []; + selectedProcedures.forEach((element) { + procedureOrderIDs.add(element["ProcedureID"].toString()); + }); + + Map mapDevice = { + 'LanguageID': languageID, + 'RequestAncillaryOrderInvoice': [ + { + 'MemberID': 102, + 'ProjectID': projectID, + 'AppointmentNo': appointmentNo, + 'OrderNo': orderNo, + 'AncillaryOrderInvoiceProcList': selectedProcedures, + } + ], + 'ProcedureOrderIds': procedureOrderIDs, + }; + + try { + dynamic apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.autoGenerateAncillaryOrdersInvoice, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + apiResponse = response; + }, + ); + if (failure != null) return Left(failure!); + return Right(apiResponse); + } catch (e) { + loggerService.logError("Unknown error in autoGenerateAncillaryOrdersInvoice: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future> applePayInsertRequest({required dynamic applePayInsertRequest}) async { + Map mapDevice = { + 'ApplePayInsertRequest': applePayInsertRequest, + }; + + try { + dynamic apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.applePayInsertRequest, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + apiResponse = response; + }, + ); + if (failure != null) return Left(failure!); + return Right(apiResponse); + } catch (e) { + loggerService.logError("Unknown error in applePayInsertRequest: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } +} diff --git a/lib/features/todo_section/todo_section_view_model.dart b/lib/features/todo_section/todo_section_view_model.dart new file mode 100644 index 0000000..0d97828 --- /dev/null +++ b/lib/features/todo_section/todo_section_view_model.dart @@ -0,0 +1,244 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_procedures_detail_response_model.dart'; +import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class TodoSectionViewModel extends ChangeNotifier { + TodoSectionRepo todoSectionRepo; + ErrorHandlerService errorHandlerService; + + TodoSectionViewModel({required this.todoSectionRepo, required this.errorHandlerService}); + + initializeTodoSectionViewModel() async { + patientAncillaryOrdersList.clear(); + isAncillaryOrdersLoading = true; + isAncillaryDetailsProceduresLoading = true; + await getPatientOnlineAncillaryOrderList(); + } + + bool isAncillaryOrdersLoading = false; + bool isAncillaryDetailsProceduresLoading = false; + bool isProcessingPayment = false; + List patientAncillaryOrdersList = []; + List patientAncillaryOrderProceduresList = []; + + void setProcessingPayment(bool value) { + isProcessingPayment = value; + notifyListeners(); + } + + Future getPatientOnlineAncillaryOrderList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + patientAncillaryOrdersList.clear(); + isAncillaryOrdersLoading = true; + notifyListeners(); + final result = await todoSectionRepo.getOnlineAncillaryOrderList(); + + result.fold( + (failure) async { + isAncillaryOrdersLoading = false; + await errorHandlerService.handleError(failure: failure); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientAncillaryOrdersList = apiResponse.data!; + isAncillaryOrdersLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future getPatientOnlineAncillaryOrderDetailsProceduresList({ + Function(dynamic)? onSuccess, + Function(String)? onError, + required int appointmentNoVida, + required int orderNo, + required int projectID, + }) async { + isAncillaryDetailsProceduresLoading = true; + notifyListeners(); + + final result = await todoSectionRepo.getOnlineAncillaryOrderDetailsProceduresList( + appointmentNoVida: appointmentNoVida, + orderNo: orderNo, + projectID: projectID, + ); + + result.fold( + (failure) async { + isAncillaryDetailsProceduresLoading = false; + await errorHandlerService.handleError(failure: failure); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientAncillaryOrderProceduresList = apiResponse.data!; + isAncillaryDetailsProceduresLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future checkPaymentStatus({ + required String transID, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + final result = await todoSectionRepo.checkPaymentStatus(transID: transID); + + result.fold( + (failure) async { + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (response) { + if (onSuccess != null) { + onSuccess(response); + } + }, + ); + } + + Future createAdvancePayment({ + required int projectID, + required double paymentAmount, + required String paymentReference, + required String paymentMethodName, + required int patientTypeID, + required String patientName, + required int patientID, + required String setupID, + required bool isAncillaryOrder, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + final result = await todoSectionRepo.createAdvancePayment( + projectID: projectID, + paymentAmount: paymentAmount, + paymentReference: paymentReference, + paymentMethodName: paymentMethodName, + patientTypeID: patientTypeID, + patientName: patientName, + patientID: patientID, + setupID: setupID, + isAncillaryOrder: isAncillaryOrder, + ); + + result.fold( + (failure) async { + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (response) { + if (onSuccess != null) { + onSuccess(response); + } + }, + ); + } + + Future addAdvancedNumberRequest({ + required String advanceNumber, + required String paymentReference, + required int appointmentID, + required int patientID, + required int patientTypeID, + required int patientOutSA, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + final result = await todoSectionRepo.addAdvancedNumberRequest( + advanceNumber: advanceNumber, + paymentReference: paymentReference, + appointmentID: appointmentID, + patientID: patientID, + patientTypeID: patientTypeID, + patientOutSA: patientOutSA, + ); + + result.fold( + (failure) async { + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (response) { + if (onSuccess != null) { + onSuccess(response); + } + }, + ); + } + + Future autoGenerateAncillaryOrdersInvoice({ + required int orderNo, + required int projectID, + required int appointmentNo, + required List selectedProcedures, + required int languageID, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + final result = await todoSectionRepo.autoGenerateAncillaryOrdersInvoice( + orderNo: orderNo, + projectID: projectID, + appointmentNo: appointmentNo, + selectedProcedures: selectedProcedures, + languageID: languageID, + ); + + result.fold( + (failure) async { + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (response) { + if (onSuccess != null) { + onSuccess(response); + } + }, + ); + } + + Future applePayInsertRequest({ + required dynamic applePayInsertRequest, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + final result = await todoSectionRepo.applePayInsertRequest( + applePayInsertRequest: applePayInsertRequest, + ); + + result.fold( + (failure) async { + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (response) { + if (onSuccess != null) { + onSuccess(response); + } + }, + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 259ce3b..547cbb6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -24,6 +24,7 @@ import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; +import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -129,6 +130,9 @@ void main() async { ), ChangeNotifierProvider( create: (_) => getIt.get(), + ), + ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 58e5ef5..37982c7 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -9,21 +9,19 @@ 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/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; -import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.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/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; 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/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; -import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; -import 'package:hmg_patient_app_new/services/cache_service.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/in_app_browser/InAppBrowser.dart'; @@ -60,8 +58,10 @@ class _AppointmentPaymentPageState extends State { scheduleMicrotask(() { payfortViewModel.initPayfortViewModel(); myAppointmentsViewModel.getTamaraInstallmentsDetails().then((val) { - if (myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && - myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) { + if (myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! >= + myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && + myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! <= + myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) { setState(() { isShowTamara = true; }); @@ -69,9 +69,10 @@ class _AppointmentPaymentPageState extends State { }); payfortViewModel.setIsApplePayConfigurationLoading(false); myAppointmentsViewModel.getPatientShareAppointment( - widget.patientAppointmentHistoryResponseModel.projectID, - widget.patientAppointmentHistoryResponseModel.clinicID, - widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false, onError: (err) { + widget.patientAppointmentHistoryResponseModel.projectID, + widget.patientAppointmentHistoryResponseModel.clinicID, + widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false, onError: (err) { Navigator.of(context).pop(); Navigator.of(context).pop(); }); @@ -109,7 +110,8 @@ class _AppointmentPaymentPageState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.asset(AppAssets.mada, width: 72.h, height: 25.h).toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), + Image.asset(AppAssets.mada, width: 72.h, height: 25.h) + .toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), SizedBox(height: 16.h), "Mada".needTranslation.toText16(isBold: true).toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), ], @@ -153,7 +155,10 @@ class _AppointmentPaymentPageState extends State { ], ).toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), SizedBox(height: 16.h), - "Visa or Mastercard".needTranslation.toText16(isBold: true).toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), + "Visa or Mastercard" + .needTranslation + .toText16(isBold: true) + .toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), ], ), SizedBox(width: 8.h), @@ -181,23 +186,27 @@ class _AppointmentPaymentPageState extends State { color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false, - ), - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.asset(AppAssets.tamara_en, width: 72.h, height: 25.h).toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), - SizedBox(height: 16.h), - "Tamara".needTranslation.toText16(isBold: true).toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), - ], - ), - SizedBox(width: 8.h), - const Spacer(), - Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( + ), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset(AppAssets.tamara_en, width: 72.h, height: 25.h) + .toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), + SizedBox(height: 16.h), + "Tamara" + .needTranslation + .toText16(isBold: true) + .toShimmer2(isShow: myAppointmentsVM.isAppointmentPatientShareLoading), + ], + ), + SizedBox(width: 8.h), + const Spacer(), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.blackColor, width: 18.h, @@ -243,7 +252,10 @@ class _AppointmentPaymentPageState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Insurance expired or inactive".needTranslation.toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(24.h, 0.h), + "Insurance expired or inactive" + .needTranslation + .toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500) + .paddingSymmetrical(24.h, 0.h), CustomButton( text: LocaleKeys.updateInsurance.tr(context: context), onPressed: () { @@ -273,7 +285,10 @@ class _AppointmentPaymentPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ "Amount before tax".needTranslation.toText14(isBold: true), - Utils.getPaymentAmountWithSymbol(myAppointmentsVM.patientAppointmentShareResponseModel!.patientShare!.toString().toText16(isBold: true), AppColors.blackColor, 13, + Utils.getPaymentAmountWithSymbol( + myAppointmentsVM.patientAppointmentShareResponseModel!.patientShare!.toString().toText16(isBold: true), + AppColors.blackColor, + 13, isSaudiCurrency: true), ], ).paddingSymmetrical(24.h, 0.h), @@ -282,7 +297,9 @@ class _AppointmentPaymentPageState extends State { children: [ "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor), Utils.getPaymentAmountWithSymbol( - myAppointmentsVM.patientAppointmentShareResponseModel!.patientTaxAmount!.toString().toText14(isBold: true, color: AppColors.greyTextColor), + myAppointmentsVM.patientAppointmentShareResponseModel!.patientTaxAmount! + .toString() + .toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13, isSaudiCurrency: true), @@ -293,7 +310,10 @@ class _AppointmentPaymentPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ "".needTranslation.toText14(isBold: true), - Utils.getPaymentAmountWithSymbol(myAppointmentsVM.patientAppointmentShareResponseModel!.patientShareWithTax!.toString().toText24(isBold: true), AppColors.blackColor, 17, + Utils.getPaymentAmountWithSymbol( + myAppointmentsVM.patientAppointmentShareResponseModel!.patientShareWithTax!.toString().toText24(isBold: true), + AppColors.blackColor, + 17, isSaudiCurrency: true), ], ).paddingSymmetrical(24.h, 0.h), @@ -372,9 +392,11 @@ class _AppointmentPaymentPageState extends State { onSuccess: (apiResponse) async { if (apiResponse.data["status"].toString().toLowerCase() == "success") { tamaraOrderID = apiResponse.data["tamara_order_id"].toString(); - await payfortViewModel.updateTamaraRequestStatus(responseMessage: "success", status: "14", clientRequestID: transID, tamaraOrderID: tamaraOrderID); + await payfortViewModel.updateTamaraRequestStatus( + responseMessage: "success", status: "14", clientRequestID: transID, tamaraOrderID: tamaraOrderID); await payfortViewModel.markAppointmentAsTamaraPaid( - projectID: widget.patientAppointmentHistoryResponseModel.projectID, appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo); + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo); await myAppointmentsViewModel.addAdvanceNumberRequest( advanceNumber: "Tamara-Advance-0000", paymentReference: tamaraOrderID, @@ -417,7 +439,8 @@ class _AppointmentPaymentPageState extends State { } }); } else { - await payfortViewModel.updateTamaraRequestStatus(responseMessage: "Failed", status: "00", clientRequestID: transID, tamaraOrderID: tamaraOrderID); + await payfortViewModel.updateTamaraRequestStatus( + responseMessage: "Failed", status: "00", clientRequestID: transID, tamaraOrderID: tamaraOrderID); LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, @@ -535,7 +558,9 @@ class _AppointmentPaymentPageState extends State { browser!, widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false, "2", - widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? myAppointmentsViewModel.patientAppointmentShareResponseModel!.clinicID.toString() : "", + widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! + ? myAppointmentsViewModel.patientAppointmentShareResponseModel!.clinicID.toString() + : "", context, myAppointmentsViewModel.patientAppointmentShareResponseModel!.appointmentDate, myAppointmentsViewModel.patientAppointmentShareResponseModel!.appointmentNo, @@ -546,7 +571,13 @@ class _AppointmentPaymentPageState extends State { startApplePay() async { showCommonBottomSheet(context, - child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); + child: Utils.getLoadingWidget(), + callBackFunc: (str) {}, + title: "", + height: ResponsiveExtension.screenHeight * 0.3, + isCloseButtonVisible: false, + isDismissible: false, + isFullScreen: false); transID = Utils.getAppointmentTransID( widget.patientAppointmentHistoryResponseModel.projectID, widget.patientAppointmentHistoryResponseModel.clinicID, @@ -556,7 +587,9 @@ class _AppointmentPaymentPageState extends State { ApplePayInsertRequest applePayInsertRequest = ApplePayInsertRequest(); await payfortViewModel.getPayfortConfigurations( - serviceId: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), projectId: widget.patientAppointmentHistoryResponseModel.projectID, integrationId: 2); + serviceId: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), + projectId: widget.patientAppointmentHistoryResponseModel.projectID, + integrationId: 2); applePayInsertRequest.clientRequestID = transID; applePayInsertRequest.clinicID = widget.patientAppointmentHistoryResponseModel.clinicID; diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index 2ea75d1..0488439 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -13,12 +13,13 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; class AppointmentDoctorCard extends StatelessWidget { - AppointmentDoctorCard( - {super.key, - required this.patientAppointmentHistoryResponseModel, - required this.onRescheduleTap, - required this.onCancelTap, - required this.onAskDoctorTap}); + const AppointmentDoctorCard({ + super.key, + required this.patientAppointmentHistoryResponseModel, + required this.onRescheduleTap, + required this.onCancelTap, + required this.onAskDoctorTap, + }); final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; final VoidCallback onRescheduleTap; diff --git a/lib/presentation/book_appointment/search_doctor_by_name.dart b/lib/presentation/book_appointment/search_doctor_by_name.dart index fabea2d..bdb6773 100644 --- a/lib/presentation/book_appointment/search_doctor_by_name.dart +++ b/lib/presentation/book_appointment/search_doctor_by_name.dart @@ -46,7 +46,7 @@ class _SearchDoctorByNameState extends State { body: Column( children: [ Expanded( - child: CollapsingListView( + child: CollapsingListView( title: "Choose Doctor".needTranslation, child: SingleChildScrollView( child: Padding( diff --git a/lib/presentation/book_appointment/widgets/doctor_card.dart b/lib/presentation/book_appointment/widgets/doctor_card.dart index dd9d6df..1a91f01 100644 --- a/lib/presentation/book_appointment/widgets/doctor_card.dart +++ b/lib/presentation/book_appointment/widgets/doctor_card.dart @@ -16,11 +16,17 @@ import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; class DoctorCard extends StatelessWidget { - DoctorCard({super.key, required this.doctorsListResponseModel, required this.isLoading, required this.bookAppointmentsViewModel}); + const DoctorCard({ + super.key, + required this.doctorsListResponseModel, + required this.isLoading, + required this.bookAppointmentsViewModel, + }); - DoctorsListResponseModel doctorsListResponseModel; - bool isLoading = false; - BookAppointmentsViewModel bookAppointmentsViewModel; + final DoctorsListResponseModel doctorsListResponseModel; + final bool isLoading; + + final BookAppointmentsViewModel bookAppointmentsViewModel; @override Widget build(BuildContext context) { @@ -55,10 +61,14 @@ class DoctorCard extends StatelessWidget { children: [ SizedBox( width: MediaQuery.of(context).size.width * 0.49, - child: (isLoading ? "Dr John Smith" : "${doctorsListResponseModel.doctorTitle} ${doctorsListResponseModel.name}").toString().toText16(isBold: true, maxlines: 1), + child: (isLoading ? "Dr John Smith" : "${doctorsListResponseModel.doctorTitle} ${doctorsListResponseModel.name}") + .toString() + .toText16(isBold: true, maxlines: 1), ).toShimmer2(isShow: isLoading), Image.network( - isLoading ? "https://hmgwebservices.com/Images/flag/SYR.png" : doctorsListResponseModel.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SYR.png", + isLoading + ? "https://hmgwebservices.com/Images/flag/SYR.png" + : doctorsListResponseModel.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SYR.png", width: 20.h, height: 15.h, fit: BoxFit.fill, @@ -79,7 +89,8 @@ class DoctorCard extends StatelessWidget { ), Expanded( flex: 1, - child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).toShimmer2(isShow: isLoading), + child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown) + .toShimmer2(isShow: isLoading), ), ], ), diff --git a/lib/presentation/home/navigation_screen.dart b/lib/presentation/home/navigation_screen.dart index 9c7566d..0599969 100644 --- a/lib/presentation/home/navigation_screen.dart +++ b/lib/presentation/home/navigation_screen.dart @@ -5,7 +5,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointme 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/presentation/todo/todo_page.dart'; +import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart'; import 'package:hmg_patient_app_new/widgets/bottom_navigation/bottom_navigation.dart'; class LandingNavigation extends StatefulWidget { diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 1be683b..a2cd333 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -173,8 +173,7 @@ class _MedicalFilePageState extends State { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.asset(appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, - width: 56.w, height: 56.h), + Image.asset(appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, width: 56.w, height: 56.h), SizedBox(width: 8.w), Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -221,13 +220,13 @@ class _MedicalFilePageState extends State { children: [ AppCustomChipWidget( labelText: "${appState.getAuthenticatedUser()!.age} Years Old", - labelPadding: EdgeInsetsDirectional.only(start: 8.w, end: 8.w), + labelPadding: EdgeInsetsDirectional.only(start: 8.w, end: 8.w), ), AppCustomChipWidget( icon: AppAssets.blood_icon, - labelText: "Blood: ${appState.getUserBloodGroup.isEmpty ? "N/A" : appState.getUserBloodGroup.isEmpty}", + labelText: "Blood: ${appState.getUserBloodGroup.isEmpty ? "N/A" : appState.getUserBloodGroup.isEmpty}", iconColor: AppColors.primaryRedColor, - labelPadding: EdgeInsetsDirectional.only(end: 8.w), + labelPadding: EdgeInsetsDirectional.only(end: 8.w), ), Consumer(builder: (context, insuranceVM, child) { return AppCustomChipWidget( @@ -236,7 +235,8 @@ class _MedicalFilePageState extends State { iconColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, textColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, iconSize: 12.w, - backgroundColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1), + backgroundColor: + insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1), labelPadding: EdgeInsetsDirectional.only(end: 8.w), ); }), @@ -738,7 +738,7 @@ class _MedicalFilePageState extends State { crossAxisCount: 3, crossAxisSpacing: 16.h, mainAxisSpacing: 16.w, - mainAxisExtent: 110.h, + mainAxisExtent: 115.h, ), physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, diff --git a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart index 0f382dc..3924761 100644 --- a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart @@ -88,7 +88,7 @@ class MedicalFileAppointmentCard extends StatelessWidget { myAppointmentsViewModel.isMyAppointmentsLoading ? Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r) : Expanded( - flex: 6, + flex: 7, child: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? getArrivedAppointmentButton(context).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading) : CustomButton( @@ -105,7 +105,8 @@ class MedicalFileAppointmentCard extends StatelessWidget { }, backgroundColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15), - borderColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + borderColor: + AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), fontSize: 14.f, fontWeight: FontWeight.w500, diff --git a/lib/presentation/medical_file/widgets/medical_file_card.dart b/lib/presentation/medical_file/widgets/medical_file_card.dart index 00d62c9..b96026b 100644 --- a/lib/presentation/medical_file/widgets/medical_file_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_card.dart @@ -30,17 +30,16 @@ class MedicalFileCard extends StatelessWidget { color: backgroundColor, borderRadius: 12.r, ), - child: Padding( - padding: EdgeInsets.all(12.w), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Utils.buildSvgWithAssets(icon: svgIcon, width: iconS, height: iconS, fit: BoxFit.contain), - SizedBox(height: 12.h), - isLargeText ? label.toText13(color: textColor, isBold: true, maxLine: 2) : label.toText11(color: textColor, isBold: true, maxLine: 2), - ], - ), + padding: EdgeInsets.all(12.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Utils.buildSvgWithAssets(icon: svgIcon, width: iconS, height: iconS, fit: BoxFit.contain), + SizedBox(height: 8.h), + isLargeText ? label.toText13(color: textColor, isBold: true, maxLine: 2) : label.toText11(color: textColor, isBold: true, maxLine: 2), + ], ), ); } diff --git a/lib/presentation/onboarding/splash_animation_screen.dart b/lib/presentation/onboarding/splash_animation_screen.dart index bca9681..7acd078 100644 --- a/lib/presentation/onboarding/splash_animation_screen.dart +++ b/lib/presentation/onboarding/splash_animation_screen.dart @@ -59,7 +59,7 @@ class _SplashAnimationScreenState extends State with Sing } } -// todo: do-not remove this code,as animation need to test on multiple screen sizes +// todo_section: do-not remove this code,as animation need to test on multiple screen sizes class AnimatedScreen extends StatefulWidget { const AnimatedScreen({super.key}); diff --git a/lib/presentation/radiology/radiology_orders_page.dart b/lib/presentation/radiology/radiology_orders_page.dart index 6662a8e..51b3343 100644 --- a/lib/presentation/radiology/radiology_orders_page.dart +++ b/lib/presentation/radiology/radiology_orders_page.dart @@ -10,15 +10,13 @@ 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/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; -import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/radiology/radiology_result_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/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; -import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; import 'package:provider/provider.dart'; import '../../features/radiology/radiology_view_model.dart'; @@ -38,7 +36,7 @@ class _RadiologyOrdersPageState extends State { @override void initState() { scheduleMicrotask(() { - radiologyViewModel.initRadiologyProvider(); + radiologyViewModel.initRadiologyViewModel(); }); super.initState(); } @@ -78,127 +76,136 @@ class _RadiologyOrdersPageState extends State { ) : model.patientRadiologyOrders.isNotEmpty ? AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: AnimatedContainer( - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - margin: EdgeInsets.symmetric(vertical: 8.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), - child: InkWell( - onTap: () { - setState(() { - expandedIndex = isExpanded ? null : index; - }); - }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AppCustomChipWidget( - labelText: LocaleKeys.resultsAvailable.tr(context: context), - backgroundColor: AppColors.successColor.withOpacity(0.15), - textColor: AppColors.successColor, - ).toShimmer2(isShow: model.isRadiologyOrdersLoading, width: 100), - SizedBox(height: 8.h), - Row( - children: [ - Image.network( - model.isRadiologyOrdersLoading - ? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png" - : model.patientRadiologyOrders[index].doctorImageURL!, - width: 24.h, - height: 24.h, - fit: BoxFit.fill, - ).circle(100).toShimmer2(isShow: model.isRadiologyOrdersLoading), - SizedBox(width: 4.h), - (model.isRadiologyOrdersLoading ? "Dr John Smith" : model.patientRadiologyOrders[index].doctorName!) - .toText16(isBold: true) - .toShimmer2(isShow: model.isRadiologyOrdersLoading) - ], - ), - SizedBox(height: 8.h), - Wrap( - direction: Axis.horizontal, - spacing: 3.h, - runSpacing: 4.h, + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), + child: InkWell( + onTap: () { + setState(() { + expandedIndex = isExpanded ? null : index; + }); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ AppCustomChipWidget( - icon: AppAssets.doctor_calendar_icon, - labelText: model.isRadiologyOrdersLoading ? "01 Jan 2025" : DateUtil.formatDateToDate(model.patientRadiologyOrders[index].orderDate!, false), - ).toShimmer2(isShow: model.isRadiologyOrdersLoading), - AppCustomChipWidget( - labelText: model.isRadiologyOrdersLoading ? "01 Jan 2025" : model.patientRadiologyOrders[index].clinicDescription!, - ).toShimmer2(isShow: model.isRadiologyOrdersLoading), + labelText: LocaleKeys.resultsAvailable.tr(context: context), + backgroundColor: AppColors.successColor.withOpacity(0.15), + textColor: AppColors.successColor, + ).toShimmer2(isShow: model.isRadiologyOrdersLoading, width: 100), + SizedBox(height: 8.h), + Row( + children: [ + Image.network( + model.isRadiologyOrdersLoading + ? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png" + : model.patientRadiologyOrders[index].doctorImageURL!, + width: 24.h, + height: 24.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: model.isRadiologyOrdersLoading), + SizedBox(width: 4.h), + (model.isRadiologyOrdersLoading + ? "Dr John Smith" + : model.patientRadiologyOrders[index].doctorName!) + .toText16(isBold: true) + .toShimmer2(isShow: model.isRadiologyOrdersLoading) + ], + ), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: model.isRadiologyOrdersLoading + ? "01 Jan 2025" + : DateUtil.formatDateToDate(model.patientRadiologyOrders[index].orderDate!, false), + ).toShimmer2(isShow: model.isRadiologyOrdersLoading), + AppCustomChipWidget( + labelText: model.isRadiologyOrdersLoading + ? "01 Jan 2025" + : model.patientRadiologyOrders[index].clinicDescription!, + ).toShimmer2(isShow: model.isRadiologyOrdersLoading), - // AppCustomChipWidget(labelText: "").toShimmer2(isShow: model.isRadiologyOrdersLoading, width: 16.h), - // AppCustomChipWidget(labelText: "").toShimmer2(isShow: model.isRadiologyOrdersLoading, width: 16.h), + // AppCustomChipWidget(labelText: "").toShimmer2(isShow: model.isRadiologyOrdersLoading, width: 16.h), + // AppCustomChipWidget(labelText: "").toShimmer2(isShow: model.isRadiologyOrdersLoading, width: 16.h), + ], + ), ], ), - ], - ), - ), - model.isRadiologyOrdersLoading - ? SizedBox.shrink() - : AnimatedCrossFade( - firstChild: SizedBox.shrink(), - secondChild: Padding( - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.only(bottom: 8.h), - child: '● ${model.patientRadiologyOrders[index].description}'.toText14(weight: FontWeight.w500), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + ), + model.isRadiologyOrdersLoading + ? SizedBox.shrink() + : AnimatedCrossFade( + firstChild: SizedBox.shrink(), + secondChild: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox(), - CustomButton( - icon: AppAssets.view_report_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - text: LocaleKeys.viewReport.tr(context: context), - onPressed: () { - Navigator.of(context).push( - CustomPageRoute( - page: RadiologyResultPage(patientRadiologyResponseModel: model.patientRadiologyOrders[index]), - ), - ); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14, - fontWeight: FontWeight.bold, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, + Padding( + padding: EdgeInsets.only(bottom: 8.h), + child: '● ${model.patientRadiologyOrders[index].description}' + .toText14(weight: FontWeight.w500), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(), + CustomButton( + icon: AppAssets.view_report_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + text: LocaleKeys.viewReport.tr(context: context), + onPressed: () { + Navigator.of(context).push( + CustomPageRoute( + page: RadiologyResultPage( + patientRadiologyResponseModel: model.patientRadiologyOrders[index]), + ), + ); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.bold, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], ), ], ), - ], + ), + crossFadeState: isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst, + duration: Duration(milliseconds: 300), ), - ), - crossFadeState: isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst, - duration: Duration(milliseconds: 300), - ), - ], + ], + ), + ), ), ), ), - ), - ), - ) - : Utils.getNoDataWidget(context, noDataText: "You don't have any radiology results yet.".needTranslation); + ) + : Utils.getNoDataWidget(context, noDataText: "You don't have any radiology results yet.".needTranslation); }, ), ], diff --git a/lib/presentation/todo/todo_page.dart b/lib/presentation/todo/todo_page.dart deleted file mode 100644 index 20f8cd4..0000000 --- a/lib/presentation/todo/todo_page.dart +++ /dev/null @@ -1,31 +0,0 @@ -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/extensions/widget_extensions.dart'; -import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; - -class ToDoPage extends StatefulWidget { - const ToDoPage({super.key}); - - @override - State createState() => _ToDoPageState(); -} - -class _ToDoPageState extends State { - @override - Widget build(BuildContext context) { - return CollapsingListView( - title: "ToDo List".needTranslation, - isLeading: false, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 16.h), - "Ancillary Orders".needTranslation.toText18(isBold: true), - - ], - ).paddingSymmetrical(24.w, 0), - ); - } -} \ No newline at end of file diff --git a/lib/presentation/todo_section/ancillary_order_payment_page.dart b/lib/presentation/todo_section/ancillary_order_payment_page.dart new file mode 100644 index 0000000..65ab778 --- /dev/null +++ b/lib/presentation/todo_section/ancillary_order_payment_page.dart @@ -0,0 +1,484 @@ +import 'dart:async'; +import 'dart:developer'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.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/payfort/payfort_view_model.dart'; +import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_procedures_detail_response_model.dart'; +import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.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/in_app_browser/InAppBrowser.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:provider/provider.dart'; + +class AncillaryOrderPaymentPage extends StatefulWidget { + final int appointmentNoVida; + final int orderNo; + final int projectID; + final List selectedProcedures; + final double totalAmount; + + const AncillaryOrderPaymentPage({ + super.key, + required this.appointmentNoVida, + required this.orderNo, + required this.projectID, + required this.selectedProcedures, + required this.totalAmount, + }); + + @override + State createState() => _AncillaryOrderPaymentPageState(); +} + +class _AncillaryOrderPaymentPageState extends State { + late PayfortViewModel payfortViewModel; + late AppState appState; + late TodoSectionViewModel todoSectionViewModel; + + MyInAppBrowser? browser; + String selectedPaymentMethod = ""; + String transID = ""; + + @override + void initState() { + scheduleMicrotask(() { + payfortViewModel.initPayfortViewModel(); + payfortViewModel.setIsApplePayConfigurationLoading(false); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + appState = getIt.get(); + todoSectionViewModel = Provider.of(context); + payfortViewModel = Provider.of(context); + + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Consumer( + builder: (context, todoVM, child) { + return Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Select Payment Method".needTranslation, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + + // Mada Payment Option + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset(AppAssets.mada, width: 72.h, height: 25.h).toShimmer2(isShow: todoVM.isProcessingPayment), + SizedBox(height: 16.h), + "Mada".needTranslation.toText16(isBold: true).toShimmer2(isShow: todoVM.isProcessingPayment), + ], + ), + SizedBox(width: 8.h), + const Spacer(), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ).toShimmer2(isShow: todoVM.isProcessingPayment), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + if (!todoVM.isProcessingPayment) { + selectedPaymentMethod = "MADA"; + _openPaymentURL("mada"); + } + }), + + SizedBox(height: 16.h), + + // Visa/Mastercard Payment Option + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Image.asset(AppAssets.visa, width: 50.h, height: 50.h), + SizedBox(width: 8.h), + Image.asset(AppAssets.Mastercard, width: 40.h, height: 40.h), + ], + ).toShimmer2(isShow: todoVM.isProcessingPayment), + SizedBox(height: 16.h), + "Visa or Mastercard".needTranslation.toText16(isBold: true).toShimmer2(isShow: todoVM.isProcessingPayment), + ], + ), + SizedBox(width: 8.h), + const Spacer(), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ).toShimmer2(isShow: todoVM.isProcessingPayment), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + if (!todoVM.isProcessingPayment) { + selectedPaymentMethod = "VISA"; + _openPaymentURL("visa"); + } + }), + ], + ), + ), + ), + ), + + // Payment Summary Footer + todoVM.isProcessingPayment ? SizedBox.shrink() : _buildPaymentSummary(), + ], + ); + }, + ), + ); + } + + Widget _buildPaymentSummary() { + // Calculate amounts + double amountBeforeTax = 0.0; + double taxAmount = 0.0; + + for (var proc in widget.selectedProcedures) { + amountBeforeTax += (proc.patientShare ?? 0); + taxAmount += (proc.patientTaxAmount ?? 0); + } + + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Consumer(builder: (context, payfortVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + "Total amount to pay".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 17.h), + + // Amount before tax + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Amount before tax".needTranslation.toText14(isBold: true), + Utils.getPaymentAmountWithSymbol( + amountBeforeTax.toString().toText16(isBold: true), + AppColors.blackColor, + 13, + isSaudiCurrency: true, + ), + ], + ).paddingSymmetrical(24.h, 0.h), + + // VAT 15% + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor), + Utils.getPaymentAmountWithSymbol( + taxAmount.toString().toText14(isBold: true, color: AppColors.greyTextColor), + AppColors.greyTextColor, + 13, + isSaudiCurrency: true, + ), + ], + ).paddingSymmetrical(24.h, 0.h), + + SizedBox(height: 17.h), + + // Total Amount + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "".needTranslation.toText14(isBold: true), + Utils.getPaymentAmountWithSymbol( + widget.totalAmount.toString().toText24(isBold: true), + AppColors.blackColor, + 17, + isSaudiCurrency: true, + ), + ], + ).paddingSymmetrical(24.h, 0.h), + + // Apple Pay Button (iOS only) + Platform.isIOS && Utils.havePrivilege(103) + ? Utils.buildSvgWithAssets( + icon: AppAssets.apple_pay_button, + width: 200.h, + height: 80.h, + fit: BoxFit.contain, + ).paddingSymmetrical(24.h, 0.h).onPress(() { + if (!todoSectionViewModel.isProcessingPayment) { + _openPaymentURL("ApplePay"); + } + }) + : SizedBox(height: 12.h), + + SizedBox(height: 12.h), + ], + ); + }), + ); + } + + void _openPaymentURL(String paymentMethod) { + todoSectionViewModel.setProcessingPayment(true); + + browser = MyInAppBrowser( + onExitCallback: _onBrowserExit, + onLoadStartCallback: _onBrowserLoadStart, + ); + + final user = appState.getAuthenticatedUser(); + transID = Utils.getAdvancePaymentTransID( + widget.projectID, + user!.patientId!, + ); + + browser!.openPaymentBrowser( + widget.totalAmount, + "Ancillary Order Payment", + transID, + widget.projectID.toString(), + user.emailAddress ?? "CustID_${user.patientId}@HMG.com", + paymentMethod, + user.patientType ?? 1, + "${user.firstName} ${user.lastName}", + user.patientId, + user, + browser!, + false, + "3", + ServiceTypeEnum.ancillaryOrder.getIdFromServiceEnum().toString(), + context, + null, + widget.appointmentNoVida, + 0, + 0, + null, + ); + } + + void _onBrowserLoadStart(String url) { + log("onBrowserLoadStart: $url"); + + for (var element in MyInAppBrowser.successURLS) { + if (url.contains(element)) { + if (browser!.isOpened()) browser!.close(); + MyInAppBrowser.isPaymentDone = true; + return; + } + } + + for (var element in MyInAppBrowser.errorURLS) { + if (url.contains(element)) { + if (browser!.isOpened()) browser!.close(); + MyInAppBrowser.isPaymentDone = false; + return; + } + } + } + + void _onBrowserExit(bool isPaymentMade) { + log("onBrowserExit Called: $isPaymentMade"); + _checkPaymentStatus(); + } + + void _checkPaymentStatus() { + LoaderBottomSheet.showLoader(loadingText: "Checking payment status, Please wait...".needTranslation); + + todoSectionViewModel.checkPaymentStatus( + transID: transID, + onSuccess: (response) { + String paymentInfo = response['Response_Message']; + + if (paymentInfo == 'Success') { + // Extract payment details from response + final paymentAmount = response['Amount'] ?? widget.totalAmount; + final fortId = response['Fort_id'] ?? transID; + final paymentMethod = response['PaymentMethod'] ?? selectedPaymentMethod; + + // Call createAdvancePayment with the payment details + _createAdvancePayment( + paymentAmount: paymentAmount is String ? double.parse(paymentAmount) : paymentAmount.toDouble(), + paymentReference: fortId, + paymentMethod: paymentMethod, + ); + } else { + LoaderBottomSheet.hideLoader(); + todoSectionViewModel.setProcessingPayment(false); + Utils.showToast(response['Response_Message']); + } + }, + onError: (error) { + LoaderBottomSheet.hideLoader(); + todoSectionViewModel.setProcessingPayment(false); + Utils.showToast(error); + }, + ); + } + + void _createAdvancePayment({ + required double paymentAmount, + required String paymentReference, + required String paymentMethod, + }) { + LoaderBottomSheet.showLoader(loadingText: "Processing payment, Please wait...".needTranslation); + + final user = appState.getAuthenticatedUser(); + + todoSectionViewModel.createAdvancePayment( + projectID: widget.projectID, + paymentAmount: paymentAmount, + paymentReference: paymentReference, + paymentMethodName: paymentMethod, + patientTypeID: user!.patientType ?? 1, + patientName: "${user.firstName} ${user.lastName}", + patientID: user.patientId!, + setupID: "010266", + isAncillaryOrder: true, + onSuccess: (response) { + // Extract advance number from response + final advanceNumber = + response['OnlineCheckInAppointments']?[0]?['AdvanceNumber'] ?? response['OnlineCheckInAppointments']?[0]?['AdvanceNumber_VP'] ?? ''; + + if (advanceNumber.isNotEmpty) { + _addAdvancedNumberRequest( + advanceNumber: advanceNumber.toString(), + paymentReference: paymentReference, + ); + } else { + LoaderBottomSheet.hideLoader(); + todoSectionViewModel.setProcessingPayment(false); + Utils.showToast("Failed to get advance number"); + } + }, + onError: (error) { + LoaderBottomSheet.hideLoader(); + todoSectionViewModel.setProcessingPayment(false); + Utils.showToast(error); + }, + ); + } + + void _addAdvancedNumberRequest({ + required String advanceNumber, + required String paymentReference, + }) { + LoaderBottomSheet.showLoader(loadingText: "Finalizing payment, Please wait...".needTranslation); + + final user = appState.getAuthenticatedUser(); + + todoSectionViewModel.addAdvancedNumberRequest( + advanceNumber: advanceNumber, + paymentReference: paymentReference, + appointmentID: 0, + patientID: user!.patientId!, + patientTypeID: user.patientType ?? 1, + patientOutSA: user.outSa ?? 0, + onSuccess: (response) { + // After adding advance number, generate invoice + _autoGenerateInvoice(); + }, + onError: (error) { + LoaderBottomSheet.hideLoader(); + todoSectionViewModel.setProcessingPayment(false); + Utils.showToast(error); + }, + ); + } + + void _autoGenerateInvoice() { + LoaderBottomSheet.showLoader(loadingText: "Generating invoice, Please wait...".needTranslation); + + List selectedProcListAPI = widget.selectedProcedures.map((element) { + return { + "ApprovalLineItemNo": element.approvalLineItemNo, + "OrderLineItemNo": element.orderLineItemNo, + "ProcedureID": element.procedureID, + }; + }).toList(); + + todoSectionViewModel.autoGenerateAncillaryOrdersInvoice( + orderNo: widget.orderNo, + projectID: widget.projectID, + appointmentNo: widget.appointmentNoVida, + selectedProcedures: selectedProcListAPI, + languageID: appState.isArabic() ? 1 : 2, + onSuccess: (response) { + LoaderBottomSheet.hideLoader(); + + final invoiceNo = response['AncillaryOrderInvoiceList']?[0]?['InvoiceNo']; + + _showSuccessDialog(invoiceNo); + }, + onError: (error) { + LoaderBottomSheet.hideLoader(); + todoSectionViewModel.setProcessingPayment(false); + Utils.showToast(error); + }, + ); + } + + void _showSuccessDialog(dynamic invoiceNo) { + todoSectionViewModel.setProcessingPayment(false); + + log("Ancillary order payment successful! Invoice #: $invoiceNo"); + + // Show success message and navigate + Utils.showToast("Payment successful! Invoice #: $invoiceNo"); + + // Navigate back to home after a short delay + Future.delayed(Duration(seconds: 2), () { + Navigator.of(context).pop(); // Close payment page + Navigator.of(context).pop(); // Close details page + }); + } +} diff --git a/lib/presentation/todo_section/ancillary_procedures_details_page.dart b/lib/presentation/todo_section/ancillary_procedures_details_page.dart new file mode 100644 index 0000000..71e7782 --- /dev/null +++ b/lib/presentation/todo_section/ancillary_procedures_details_page.dart @@ -0,0 +1,630 @@ +import 'dart:async'; + +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.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/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/todo_section/models/resp_models/ancillary_order_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_procedures_detail_response_model.dart'; +import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/todo_section/ancillary_order_payment_page.dart'; +import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.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/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +class AncillaryProceduresDetailsList extends StatefulWidget { + final int appointmentNoVida; + final int orderNo; + final int projectID; + + const AncillaryProceduresDetailsList({ + super.key, + required this.appointmentNoVida, + required this.orderNo, + required this.projectID, + }); + + @override + State createState() => _AncillaryProceduresDetailsListState(); +} + +class _AncillaryProceduresDetailsListState extends State { + late TodoSectionViewModel todoSectionViewModel; + late AppState appState; + List selectedProcedures = []; + + @override + void initState() { + super.initState(); + appState = getIt.get(); + todoSectionViewModel = context.read(); + scheduleMicrotask(() async { + await todoSectionViewModel.getPatientOnlineAncillaryOrderDetailsProceduresList( + appointmentNoVida: widget.appointmentNoVida, + orderNo: widget.orderNo, + projectID: widget.projectID, + onSuccess: (response) { + _autoSelectEligibleProcedures(); + }, + ); + }); + } + + void _autoSelectEligibleProcedures() { + selectedProcedures.clear(); + if (todoSectionViewModel.patientAncillaryOrderProceduresList.isNotEmpty) { + final procedures = todoSectionViewModel.patientAncillaryOrderProceduresList[0].ancillaryOrderProcDetailsList; + if (procedures != null) { + for (var proc in procedures) { + if (!_isProcedureDisabled(proc)) { + selectedProcedures.add(proc); + } + } + } + } + setState(() {}); + } + + bool _isProcedureDisabled(AncillaryOrderProcDetail procedure) { + return (procedure.isApprovalRequired == true && procedure.isApprovalCreated == false) || + (procedure.isApprovalCreated == true && procedure.approvalNo == 0) || + (procedure.isApprovalRequired == true && procedure.isApprovalCreated == true && procedure.approvalNo == 0); + } + + bool _isProcedureSelected(AncillaryOrderProcDetail procedure) { + return selectedProcedures.contains(procedure); + } + + void _toggleProcedureSelection(AncillaryOrderProcDetail procedure) { + setState(() { + if (_isProcedureSelected(procedure)) { + selectedProcedures.remove(procedure); + } else { + selectedProcedures.add(procedure); + } + }); + } + + String _getApprovalStatusText(AncillaryOrderProcDetail procedure) { + if (procedure.isApprovalRequired == false) { + return "Cash"; + } else { + if (procedure.isApprovalCreated == true && procedure.approvalNo != 0) { + return "Approved"; + } else if (procedure.isApprovalRequired == true && procedure.isApprovalCreated == true && procedure.approvalNo == 0) { + return "Approval Rejected - Please visit receptionist"; + } else { + return "Sent For Approval"; + } + } + } + + double _getTotalAmount() { + double total = 0.0; + for (var proc in selectedProcedures) { + total += (proc.patientShareWithTax ?? 0); + } + return total; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Consumer(builder: (context, viewModel, child) { + AncillaryOrderProcedureItem? orderData; + if (viewModel.patientAncillaryOrderProceduresList.isNotEmpty) { + orderData = viewModel.patientAncillaryOrderProceduresList[0]; + } + + return Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Ancillary Order Details".needTranslation, + child: viewModel.isAncillaryDetailsProceduresLoading + ? _buildLoadingShimmer().paddingSymmetrical(24.w, 0) + : viewModel.patientAncillaryOrderProceduresList.isEmpty + ? _buildDefaultEmptyState(context).paddingSymmetrical(24.w, 0) + : SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + if (orderData != null) _buildPatientInfoCard(orderData), + SizedBox(height: 16.h), + if (orderData != null) _buildProceduresSection(orderData), + ], + ).paddingSymmetrical(24.w, 0), + ), + ), + ), + if (orderData != null) _buildStickyPaymentButton(orderData), + ], + ); + }), + ); + } + + Widget _buildLoadingShimmer() { + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: 3, + itemBuilder: (context, index) { + return AncillaryOrderCard( + order: AncillaryOrderItem(), + isLoading: true, + ); + }, + ); + } + + Widget _buildDefaultEmptyState(BuildContext context) { + return Center( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 40.h), + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + hasShadow: false, + ), + child: Utils.getNoDataWidget( + context, + noDataText: "No Procedures available for the selected order.".needTranslation, + isSmallWidget: true, + width: 62.w, + height: 62.h, + ), + ), + ), + ); + } + + Widget _buildPatientInfoCard(AncillaryOrderProcedureItem orderData) { + final user = appState.getAuthenticatedUser(); + final patientName = orderData.patientName ?? user?.firstName ?? "N/A"; + final patientMRN = orderData.patientID ?? user?.patientId; + final nationalID = user?.patientIdentificationNo ?? ""; + + // Determine gender for profile image (assuming 1 = male, 2 = female) + final gender = user?.gender ?? 1; + + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: Column( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header Row with Profile Image, Name, and QR Code + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset( + gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, + width: 56.w, + height: 56.h, + ), + SizedBox(width: 12.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + patientName.toText18( + isBold: true, + weight: FontWeight.w600, + textOverflow: TextOverflow.ellipsis, + maxlines: 2, + ), + ], + ), + ), + ], + ), + + SizedBox(height: 12.h), + + Wrap( + alignment: WrapAlignment.start, + spacing: 4.w, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + // icon: AppAssets.file_icon, + labelText: "MRN: ${patientMRN ?? 'N/A'}", + iconSize: 12.w, + ), + + // National ID + if (nationalID.isNotEmpty) + AppCustomChipWidget( + // icon: AppAssets.card_user, + labelText: "ID: $nationalID", + iconSize: 12.w, + ), + + // Appointment Number + if (orderData.appointmentNo != null) + AppCustomChipWidget( + // icon: AppAssets.calendar, + labelText: "Appt #: ${orderData.appointmentNo}", + iconSize: 12.w, + ), + + // Order Number + if (orderData.ancillaryOrderProcDetailsList?.firstOrNull?.orderNo != null) + AppCustomChipWidget( + labelText: "Order #: ${orderData.ancillaryOrderProcDetailsList!.first.orderNo}", + ), + + // Blood Group + if (user?.bloodGroup != null && user!.bloodGroup!.isNotEmpty) + AppCustomChipWidget( + // icon: AppAssets.blood_icon, + labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 8.w), + labelText: "Blood: ${user.bloodGroup}", + iconColor: AppColors.primaryRedColor, + ), + + // Insurance Company (if applicable) + if (orderData.companyName != null && orderData.companyName!.isNotEmpty) + AppCustomChipWidget( + icon: AppAssets.insurance_active_icon, + labelText: orderData.companyName!, + iconColor: AppColors.successColor, + backgroundColor: AppColors.successColor.withValues(alpha: 0.15), + iconSize: 12.w, + labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 8.w), + ), + + // Policy Number + if (orderData.insurancePolicyNo != null && orderData.insurancePolicyNo!.isNotEmpty) + AppCustomChipWidget( + labelText: "Policy: ${orderData.insurancePolicyNo}", + ), + + AppCustomChipWidget( + labelText: "Doctor: ${orderData.doctorName ?? "N/A"}", + ), + + if (orderData.clinicName != null && orderData.clinicName!.isNotEmpty) + AppCustomChipWidget( + labelText: "Clinic: ${orderData.clinicName!}", + ), + if (orderData.clinicName != null && orderData.clinicName!.isNotEmpty) + AppCustomChipWidget( + labelText: "Date: ${DateFormat('MMM dd, yyyy').format(orderData.appointmentDate!)}", + ), + ], + ), + + // SizedBox(height: 12.h), + // + // // Additional Details Section + // Container( + // padding: EdgeInsets.all(12.h), + // decoration: BoxDecoration( + // color: AppColors.bgScaffoldColor, + // borderRadius: BorderRadius.circular(12.r), + // ), + // child: Column( + // children: [ + // _buildInfoRow( + // "Doctor".needTranslation, + // orderData.doctorName ?? "N/A", + // ), + // if (orderData.clinicName != null && orderData.clinicName!.isNotEmpty) + // _buildInfoRow( + // "Clinic".needTranslation, + // orderData.clinicName!, + // ), + // if (orderData.appointmentDate != null) + // _buildInfoRow( + // "Appointment Date".needTranslation, + // DateFormat('MMM dd, yyyy').format(orderData.appointmentDate!), + // ), + // ], + // ), + // ), + ], + ).paddingOnly(top: 16.h, right: 16.w, left: 16.w, bottom: 12.h), + + // Divider + Container(height: 1, color: AppColors.dividerColor), + + // Summary Section + ], + ), + ); + } + + Widget _buildSummarySection(AncillaryOrderProcedureItem orderData) { + final totalProcedures = orderData.ancillaryOrderProcDetailsList?.length ?? 0; + final selectedCount = selectedProcedures.length; + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Procedures".needTranslation.toText12( + color: AppColors.textColorLight, + fontWeight: FontWeight.w600, + ), + "$selectedCount of $totalProcedures selected".toText14( + isBold: true, + weight: FontWeight.bold, + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + "Total Amount".needTranslation.toText12( + color: AppColors.textColorLight, + fontWeight: FontWeight.w600, + ), + Row( + children: [ + _getTotalAmount().toStringAsFixed(2).toText14( + isBold: true, + weight: FontWeight.bold, + color: AppColors.primaryRedColor, + ), + SizedBox(width: 4.w), + "SAR".toText12(color: AppColors.textColorLight), + ], + ), + ], + ), + ], + ); + } + + Widget _buildInfoRow(String label, String value) { + return Padding( + padding: EdgeInsets.only(bottom: 8.h), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 2, + child: "$label:".toText12(color: AppColors.textColorLight, fontWeight: FontWeight.w600), + ), + SizedBox(width: 8.w), + Expanded( + flex: 3, + child: value.toText12(color: AppColors.textColor, fontWeight: FontWeight.w600), + ), + ], + ), + ); + } + + Widget _buildProceduresSection(AncillaryOrderProcedureItem orderData) { + if (orderData.ancillaryOrderProcDetailsList == null || orderData.ancillaryOrderProcDetailsList!.isEmpty) { + return SizedBox.shrink(); + } + + // Group procedures by category + final groupedProcedures = groupBy( + orderData.ancillaryOrderProcDetailsList!, + (AncillaryOrderProcDetail proc) => proc.procedureCategoryName ?? "Other", + ); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: groupedProcedures.entries.map((entry) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + entry.key.toText18(isBold: true), + SizedBox(height: 12.h), + ...entry.value.map((procedure) => _buildProcedureCard(procedure)), + SizedBox(height: 16.h), + ], + ); + }).toList(), + ); + } + + Widget _buildProcedureCard(AncillaryOrderProcDetail procedure) { + final isDisabled = _isProcedureDisabled(procedure); + final isSelected = _isProcedureSelected(procedure); + + return AnimationConfiguration.staggeredList( + position: 0, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.only(bottom: 12.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: isDisabled ? AppColors.greyColor : AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: !isDisabled, + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: isDisabled ? null : () => _toggleProcedureSelection(procedure), + borderRadius: BorderRadius.circular(24.h), + child: Container( + padding: EdgeInsets.all(14.h), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(24.h)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (!isDisabled) + Padding( + padding: EdgeInsets.only(right: 8.w), + child: Checkbox( + value: isSelected, + onChanged: (v) => _toggleProcedureSelection(procedure), + activeColor: AppColors.primaryRedColor, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + visualDensity: VisualDensity.compact, + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (procedure.procedureName ?? "N/A").toText14(isBold: true, maxlines: 2), + ], + ), + ), + ], + ), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 8.h, + children: [ + AppCustomChipWidget( + labelText: _getApprovalStatusText(procedure), + // backgroundColor: statusColor, + ), + if (procedure.procedureID != null) + AppCustomChipWidget( + labelText: "ID: ${procedure.procedureID}", + ), + if (procedure.isCovered == true) + AppCustomChipWidget( + labelText: "Covered".needTranslation, + backgroundColor: AppColors.successColor.withValues(alpha: 0.1), + textColor: AppColors.successColor, + ), + ], + ), + SizedBox(height: 12.h), + Container(height: 1, color: AppColors.dividerColor), + SizedBox(height: 12.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Price".needTranslation.toText10(color: AppColors.textColorLight), + SizedBox(height: 4.h), + Row( + children: [ + (procedure.patientShare ?? 0).toStringAsFixed(2).toText13(weight: FontWeight.w600), + SizedBox(width: 4.w), + "SAR".toText10(color: AppColors.textColorLight), + ], + ), + ], + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "VAT (15%)".needTranslation.toText10(color: AppColors.textColorLight), + SizedBox(height: 4.h), + Row( + children: [ + (procedure.patientTaxAmount ?? 0).toStringAsFixed(2).toText13(weight: FontWeight.w600), + SizedBox(width: 4.w), + "SAR".toText10(color: AppColors.textColorLight), + ], + ), + ], + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Total".needTranslation.toText10(color: AppColors.textColorLight), + SizedBox(height: 4.h), + Row( + children: [ + (procedure.patientShareWithTax ?? 0).toStringAsFixed(2).toText13( + isBold: true, + weight: FontWeight.bold, + ), + SizedBox(width: 4.w), + "SAR".toText10(color: AppColors.textColorLight), + ], + ), + ], + ), + ), + ], + ), + ], + ), + ), + ), + ), + ), + ), + )); + } + + Widget _buildStickyPaymentButton(orderData) { + final isButtonEnabled = selectedProcedures.isNotEmpty; + return Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(height: 16.h), + _buildSummarySection(orderData), + SizedBox(height: 16.h), + CustomButton( + borderWidth: 0, + backgroundColor: AppColors.infoLightColor, + text: "Proceed to Payment".needTranslation, + onPressed: () { + // Navigate to payment page with selected procedures + Navigator.of(context).push( + CustomPageRoute( + page: AncillaryOrderPaymentPage( + appointmentNoVida: widget.appointmentNoVida, + orderNo: widget.orderNo, + projectID: widget.projectID, + selectedProcedures: selectedProcedures, + totalAmount: _getTotalAmount(), + ), + ), + ); + }, + isDisabled: !isButtonEnabled, + textColor: AppColors.whiteColor, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(vertical: 16.h), + ), + SizedBox(height: 22.h), + ], + ).paddingSymmetrical(24.w, 0); + } +} diff --git a/lib/presentation/todo_section/todo_page.dart b/lib/presentation/todo_section/todo_page.dart new file mode 100644 index 0000000..9586036 --- /dev/null +++ b/lib/presentation/todo_section/todo_page.dart @@ -0,0 +1,88 @@ +import 'dart:async'; +import 'dart:developer'; + +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/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/todo_section/ancillary_procedures_details_page.dart'; +import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.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'; +import 'package:provider/provider.dart'; + +class ToDoPage extends StatefulWidget { + const ToDoPage({super.key}); + + @override + State createState() => _ToDoPageState(); +} + +class _ToDoPageState extends State { + @override + void initState() { + final TodoSectionViewModel todoSectionViewModel = context.read(); + scheduleMicrotask(() async { + await todoSectionViewModel.initializeTodoSectionViewModel(); + }); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + Widget _buildLoadingShimmer() { + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: 3, + itemBuilder: (context, index) { + return AncillaryOrderCard( + order: AncillaryOrderItem(), + isLoading: true, + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: "ToDo List".needTranslation, + isLeading: false, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + "Ancillary Orders".needTranslation.toText18(isBold: true), + Consumer( + builder: (BuildContext context, TodoSectionViewModel todoSectionViewModel, Widget? child) { + return todoSectionViewModel.isAncillaryOrdersLoading + ? _buildLoadingShimmer() + : AncillaryOrdersList( + orders: todoSectionViewModel.patientAncillaryOrdersList, + onCheckIn: (order) => log("Check-in for order: ${order.orderNo}"), + onViewDetails: (order) async { + Navigator.of(context).push(CustomPageRoute( + page: AncillaryProceduresDetailsList( + appointmentNoVida: order.appointmentNo ?? 0, + orderNo: order.orderNo ?? 0, + projectID: 15, + // TODO: NEED to Confirm about projectID + ))); + log("View details for order: ${order.orderNo}"); + }, + ); + }, + ), + ], + ).paddingSymmetrical(24.w, 0), + ), + ); + } +} diff --git a/lib/presentation/todo_section/widgets/ancillary_orders_list.dart b/lib/presentation/todo_section/widgets/ancillary_orders_list.dart new file mode 100644 index 0000000..a1c99d9 --- /dev/null +++ b/lib/presentation/todo_section/widgets/ancillary_orders_list.dart @@ -0,0 +1,276 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.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/todo_section/models/resp_models/ancillary_order_list_response_model.dart'; +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'; + +class AncillaryOrdersList extends StatelessWidget { + final List orders; + final Function(AncillaryOrderItem order)? onCheckIn; + final Function(AncillaryOrderItem order)? onViewDetails; + + const AncillaryOrdersList({ + super.key, + required this.orders, + this.onCheckIn, + this.onViewDetails, + }); + + @override + Widget build(BuildContext context) { + // Show empty state + if (orders.isEmpty) { + return _buildDefaultEmptyState(context); + } + + // Show orders list + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: orders.length, + separatorBuilder: (BuildContext context, int index) => SizedBox(height: 12.h), + itemBuilder: (context, index) { + final order = orders[index]; + + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: AncillaryOrderCard( + order: order, + isLoading: false, + onCheckIn: onCheckIn != null ? () => onCheckIn!(order) : null, + onViewDetails: onViewDetails != null ? () => onViewDetails!(order) : null, + )), + ), + ), + ); + }, + ); + } + + Widget _buildDefaultEmptyState(BuildContext context) { + return Center( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 40.h), + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + hasShadow: false, + ), + child: Utils.getNoDataWidget( + context, + noDataText: "You don't have any ancillary orders yet.".needTranslation, + isSmallWidget: true, + width: 62.w, + height: 62.h, + ), + ), + ), + ); + } +} + +class AncillaryOrderCard extends StatelessWidget { + const AncillaryOrderCard({ + super.key, + required this.order, + this.isLoading = false, + this.onCheckIn, + this.onViewDetails, + }); + + final AncillaryOrderItem order; + final bool isLoading; + final VoidCallback? onCheckIn; + final VoidCallback? onViewDetails; + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only(bottom: 12.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header Row with Order Number and Date + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + if (!isLoading) + "Order #".needTranslation.toText14( + color: AppColors.textColorLight, + weight: FontWeight.w500, + ), + SizedBox(width: 4.w), + (isLoading ? "12345" : "${order.orderNo ?? '-'}").toText16(isBold: true).toShimmer2(isShow: isLoading), + ], + ), + if (order.orderDate != null || isLoading) + (isLoading ? "Jan 15, 2024" : DateFormat('MMM dd, yyyy').format(order.orderDate!)) + .toText12(color: AppColors.textColorLight) + .toShimmer2(isShow: isLoading), + ], + ), + + SizedBox(height: 12.h), + + // Doctor and Clinic Info + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Doctor Name + if (order.doctorName != null || isLoading) + (isLoading ? "Dr. John Smith" : order.doctorName!) + .toString() + .toText14(isBold: true, maxlines: 2) + .toShimmer2(isShow: isLoading), + + SizedBox(height: 4.h), + + // Clinic Name + if (order.clinicName != null || isLoading) + (isLoading ? "Cardiology Clinic" : order.clinicName!) + .toString() + .toText12( + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + maxLine: 2, + ) + .toShimmer2(isShow: isLoading), + ], + ), + ), + ], + ), + + SizedBox(height: 12.h), + + // Chips for Appointment Info and Status + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + // Appointment Date + if (order.appointmentDate != null || isLoading) + AppCustomChipWidget( + icon: AppAssets.calendar, + labelText: + isLoading ? "Date: Jan 20, 2024" : "Date: ${DateFormat('MMM dd, yyyy').format(order.appointmentDate!)}".needTranslation, + ).toShimmer2(isShow: isLoading), + + // Appointment Number + if (order.appointmentNo != null || isLoading) + AppCustomChipWidget( + labelText: isLoading ? "Appt #: 98765" : "Appt #: ${order.appointmentNo}".needTranslation, + ).toShimmer2(isShow: isLoading), + + // Invoice Number + if (order.invoiceNo != null || isLoading) + AppCustomChipWidget( + labelText: isLoading ? "Invoice: 45678" : "Invoice: ${order.invoiceNo}".needTranslation, + ).toShimmer2(isShow: isLoading), + + // Queued Status + if (order.isQueued == true || isLoading) + AppCustomChipWidget( + labelText: "Queued".needTranslation, + ).toShimmer2(isShow: isLoading), + + // Check-in Available Status + if (order.isCheckInAllow == true || isLoading) + AppCustomChipWidget( + labelText: "Check-in Ready".needTranslation, + ).toShimmer2(isShow: isLoading), + ], + ), + + SizedBox(height: 12.h), + + // Action Buttons + Row( + children: [ + // Check-in Button (if available) + if (order.isCheckInAllow == true || isLoading) + Expanded( + child: CustomButton( + text: "Check In".needTranslation, + onPressed: () { + if (isLoading) { + return; + } else if (onCheckIn != null) { + onCheckIn!(); + } + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 10.r, + padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), + height: 40.h, + ).toShimmer2(isShow: isLoading), + ), + + if (order.isCheckInAllow == true || isLoading) SizedBox(width: 8.w), + + // View Details Button + Expanded( + child: CustomButton( + text: "View Details".needTranslation, + onPressed: () { + if (isLoading) { + return; + } else if (onViewDetails != null) { + onViewDetails!(); + } + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 10.r, + padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), + height: 40.h, + icon: AppAssets.arrow_forward, + iconColor: AppColors.primaryRedColor, + iconSize: 15.h, + ).toShimmer2(isShow: isLoading), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart b/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart new file mode 100644 index 0000000..ba2f94d --- /dev/null +++ b/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart @@ -0,0 +1,274 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.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/todo_section/models/resp_models/ancillary_order_list_response_model.dart'; +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'; + +class AncillaryProceduresList extends StatelessWidget { + final List orders; + final Function(AncillaryOrderItem order)? onCheckIn; + final Function(AncillaryOrderItem order)? onViewDetails; + + const AncillaryProceduresList({ + super.key, + required this.orders, + this.onCheckIn, + this.onViewDetails, + }); + + @override + Widget build(BuildContext context) { + // Show empty state + if (orders.isEmpty) { + return _buildDefaultEmptyState(context); + } + + // Show orders list + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: orders.length, + itemBuilder: (context, index) { + final order = orders[index]; + + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: AncillaryOrderCard( + order: order, + isLoading: false, + onCheckIn: onCheckIn != null ? () => onCheckIn!(order) : null, + onViewDetails: onViewDetails != null ? () => onViewDetails!(order) : null, + )), + ), + ), + ); + }, + ); + } + + Widget _buildDefaultEmptyState(BuildContext context) { + return Center( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 40.h), + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + hasShadow: false, + ), + child: Utils.getNoDataWidget( + context, + noDataText: "You don't have any ancillary orders yet.".needTranslation, + isSmallWidget: true, + width: 62.w, + height: 62.h, + ), + ), + ), + ); + } +} + +class AncillaryOrderCard extends StatelessWidget { + const AncillaryOrderCard({ + super.key, + required this.order, + this.isLoading = false, + this.onCheckIn, + this.onViewDetails, + }); + + final AncillaryOrderItem order; + final bool isLoading; + final VoidCallback? onCheckIn; + final VoidCallback? onViewDetails; + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only(bottom: 12.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header Row with Order Number and Date + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + "Order #".needTranslation.toText14( + color: AppColors.textColorLight, + weight: FontWeight.w500, + ), + SizedBox(width: 4.w), + (isLoading ? "12345" : "${order.orderNo ?? '-'}").toText16(isBold: true).toShimmer2(isShow: isLoading), + ], + ), + if (order.orderDate != null || isLoading) + (isLoading ? "Jan 15, 2024" : DateFormat('MMM dd, yyyy').format(order.orderDate!)) + .toText12(color: AppColors.textColorLight) + .toShimmer2(isShow: isLoading), + ], + ), + + SizedBox(height: 12.h), + + // Doctor and Clinic Info + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Doctor Name + if (order.doctorName != null || isLoading) + (isLoading ? "Dr. John Smith" : order.doctorName!) + .toString() + .toText14(isBold: true, maxlines: 2) + .toShimmer2(isShow: isLoading), + + SizedBox(height: 4.h), + + // Clinic Name + if (order.clinicName != null || isLoading) + (isLoading ? "Cardiology Clinic" : order.clinicName!) + .toString() + .toText12( + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + maxLine: 2, + ) + .toShimmer2(isShow: isLoading), + ], + ), + ), + ], + ), + + SizedBox(height: 12.h), + + // Chips for Appointment Info and Status + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + // Appointment Date + if (order.appointmentDate != null || isLoading) + AppCustomChipWidget( + icon: AppAssets.calendar, + labelText: + isLoading ? "Date: Jan 20, 2024" : "Date: ${DateFormat('MMM dd, yyyy').format(order.appointmentDate!)}".needTranslation, + ).toShimmer2(isShow: isLoading), + + // Appointment Number + if (order.appointmentNo != null || isLoading) + AppCustomChipWidget( + labelText: isLoading ? "Appt #: 98765" : "Appt #: ${order.appointmentNo}".needTranslation, + ).toShimmer2(isShow: isLoading), + + // Invoice Number + if (order.invoiceNo != null || isLoading) + AppCustomChipWidget( + labelText: isLoading ? "Invoice: 45678" : "Invoice: ${order.invoiceNo}".needTranslation, + ).toShimmer2(isShow: isLoading), + + // Queued Status + if (order.isQueued == true || isLoading) + AppCustomChipWidget( + labelText: "Queued".needTranslation, + ).toShimmer2(isShow: isLoading), + + // Check-in Available Status + if (order.isCheckInAllow == true || isLoading) + AppCustomChipWidget( + labelText: "Check-in Ready".needTranslation, + ).toShimmer2(isShow: isLoading), + ], + ), + + SizedBox(height: 12.h), + + // Action Buttons + Row( + children: [ + // Check-in Button (if available) + if (order.isCheckInAllow == true || isLoading) + Expanded( + child: CustomButton( + text: "Check In".needTranslation, + onPressed: () { + if (isLoading) { + return; + } else if (onCheckIn != null) { + onCheckIn!(); + } + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 10.r, + padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), + height: 40.h, + ).toShimmer2(isShow: isLoading), + ), + + if (order.isCheckInAllow == true || isLoading) SizedBox(width: 8.w), + + // View Details Button + Expanded( + child: CustomButton( + text: "View Details".needTranslation, + onPressed: () { + if (isLoading) { + return; + } else if (onViewDetails != null) { + onViewDetails!(); + } + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 10.r, + padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), + height: 40.h, + icon: AppAssets.arrow_forward, + iconColor: AppColors.primaryRedColor, + iconSize: 15.h, + ).toShimmer2(isShow: isLoading), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/services/analytics/flows/app_nav.dart b/lib/services/analytics/flows/app_nav.dart index bd9186c..75570c6 100644 --- a/lib/services/analytics/flows/app_nav.dart +++ b/lib/services/analytics/flows/app_nav.dart @@ -18,7 +18,7 @@ class AppNav{ if(tabIndex == 3) nav_name = "my family"; if(tabIndex == 4) - nav_name = "todo list"; + nav_name = "todo_section list"; if(tabIndex == 5) nav_name = "help"; diff --git a/lib/services/analytics/flows/todo_list.dart b/lib/services/analytics/flows/todo_list.dart index c1c874b..1cc7644 100644 --- a/lib/services/analytics/flows/todo_list.dart +++ b/lib/services/analytics/flows/todo_list.dart @@ -68,7 +68,7 @@ class TodoList{ // to_do_list_confirm_appointment(AppoitmentAllHistoryResultList appointment){ // logger('confirm_appointment', parameters: { // 'appointment_type' : appointment.isLiveCareAppointment! ? 'livecare' : 'regular', - // 'flow_type' : 'todo list', + // 'flow_type' : 'todo_section list', // 'clinic_type_online' : appointment.clinicName, // 'hospital_name' : appointment.projectName, // 'doctor_name' : (appointment.doctorName == null || appointment.doctorName == '') ? appointment.doctorNameObj : appointment.doctorName, diff --git a/lib/services/cache_service.dart b/lib/services/cache_service.dart index 8a015d8..986baa4 100644 --- a/lib/services/cache_service.dart +++ b/lib/services/cache_service.dart @@ -95,7 +95,7 @@ class CacheServiceImp implements CacheService { if (string == null) return null; return json.decode(string); } catch (ex) { - loggerService.errorLogs(ex.toString()); + loggerService.logError(ex.toString()); return null; } } @@ -105,7 +105,7 @@ class CacheServiceImp implements CacheService { try { await sharedPreferences.setString(key, json.encode(value)); } catch (ex) { - loggerService.errorLogs(ex.toString()); + loggerService.logError(ex.toString()); } } diff --git a/lib/services/error_handler_service.dart b/lib/services/error_handler_service.dart index aeefefb..bda1727 100644 --- a/lib/services/error_handler_service.dart +++ b/lib/services/error_handler_service.dart @@ -25,33 +25,33 @@ class ErrorHandlerServiceImp implements ErrorHandlerService { @override Future handleError({required Failure failure, Function()? onOkPressed, Function(Failure)? onUnHandledFailure, Function(Failure)? onMessageStatusFailure}) async { if (failure is APIException) { - loggerService.errorLogs("API Exception: ${failure.message}"); + loggerService.logError("API Exception: ${failure.message}"); } else if (failure is ServerFailure) { - loggerService.errorLogs("URL: ${failure.url} \n Server Failure: ${failure.message}"); + loggerService.logError("URL: ${failure.url} \n Server Failure: ${failure.message}"); await _showDialog(failure, title: "Server Failure"); } else if (failure is DataParsingFailure) { - loggerService.errorLogs("Data Parsing Failure: ${failure.message}"); + loggerService.logError("Data Parsing Failure: ${failure.message}"); await _showDialog(failure, title: "Data Error"); } else if (failure is StatusCodeFailure) { - loggerService.errorLogs("StatusCode Failure: ${failure.message}"); + loggerService.logError("StatusCode Failure: ${failure.message}"); await _showDialog(failure, title: "StatusCodeFailure"); } else if (failure is ConnectivityFailure) { - loggerService.errorLogs("ConnectivityFailure : ${failure.message}"); + loggerService.logError("ConnectivityFailure : ${failure.message}"); await _showDialog(failure, title: "ConnectivityFailure ", onOkPressed: () {}); } else if (failure is UnAuthenticatedUserFailure) { - loggerService.errorLogs("URL: ${failure.url} \n UnAuthenticatedUser Failure: ${failure.message}"); + loggerService.logError("URL: ${failure.url} \n UnAuthenticatedUser Failure: ${failure.message}"); await _showDialog(failure, title: "UnAuthenticatedUser Failure", onOkPressed: () => navigationService.replaceAllRoutesAndNavigateToLanding()); } else if (failure is AppUpdateFailure) { - loggerService.errorLogs("AppUpdateFailure : ${failure.message}"); + loggerService.logError("AppUpdateFailure : ${failure.message}"); await _showDialog(failure, title: "AppUpdateFailure Error", onOkPressed: () => navigationService.replaceAllRoutesAndNavigateToLanding()); } else if (failure is HttpException) { - loggerService.errorLogs("Http Exception: ${failure.message}"); + loggerService.logError("Http Exception: ${failure.message}"); await _showDialog(failure, title: "Network Error"); } else if (failure is UnknownFailure) { - loggerService.errorLogs("URL: ${failure.url} \n Unknown Failure: ${failure.message}"); + loggerService.logError("URL: ${failure.url} \n Unknown Failure: ${failure.message}"); await _showDialog(failure, title: "Unknown Failure"); } else if (failure is InvalidCredentials) { - loggerService.errorLogs("Invalid Credentials : ${failure.message}"); + loggerService.logError("Invalid Credentials : ${failure.message}"); await _showDialog(failure, title: "Invalid Credentials "); } else if (failure is UserIntimationFailure) { if (onUnHandledFailure != null) { @@ -66,7 +66,7 @@ class ErrorHandlerServiceImp implements ErrorHandlerService { await _showDialog(failure, title: "MessageStatusFailure", onOkPressed: onOkPressed); } } else { - loggerService.errorLogs("Unhandled failure type: $failure"); + loggerService.logError("Unhandled failure type: $failure"); await _showDialog(failure, title: "Unhandled Error", onOkPressed: onOkPressed); } } diff --git a/lib/services/logger_service.dart b/lib/services/logger_service.dart index 9df6633..617539a 100644 --- a/lib/services/logger_service.dart +++ b/lib/services/logger_service.dart @@ -1,7 +1,7 @@ import 'package:logger/logger.dart'; abstract class LoggerService { - void errorLogs(String message); + void logError(String message); void logInfo(String message); } @@ -12,7 +12,7 @@ class LoggerServiceImp implements LoggerService { LoggerServiceImp({required this.logger}); @override - void errorLogs(String message) { + void logError(String message) { logger.e(message); } diff --git a/lib/widgets/buttons/custom_button.dart b/lib/widgets/buttons/custom_button.dart index f236bd9..03db819 100644 --- a/lib/widgets/buttons/custom_button.dart +++ b/lib/widgets/buttons/custom_button.dart @@ -64,7 +64,7 @@ class CustomButton extends StatelessWidget { width: width, padding: padding, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: isDisabled ? backgroundColor.withOpacity(.5) : backgroundColor, + color: isDisabled ? backgroundColor.withValues(alpha: .5) : backgroundColor, borderRadius: radius, customBorder: BorderRadius.circular(radius), side: borderSide ?? BorderSide(width: borderWidth.h, color: isDisabled ? borderColor.withValues(alpha: 0.5) : borderColor)), @@ -74,7 +74,7 @@ class CustomButton extends StatelessWidget { children: [ if (icon != null) Padding( - padding: text.isNotEmpty ? EdgeInsets.only(right: 8.h, left: 8.h) : EdgeInsets.zero, + padding: text.isNotEmpty ? EdgeInsets.only(right: 6.w, left: 6.w) : EdgeInsets.zero, child: Utils.buildSvgWithAssets(icon: icon!, iconColor: iconColor, isDisabled: isDisabled, width: iconS, height: iconS), ), Visibility( @@ -86,7 +86,7 @@ class CustomButton extends StatelessWidget { overflow: textOverflow, style: context.dynamicTextStyle( fontSize: fontS, - color: isDisabled ? textColor.withOpacity(0.5) : textColor, + color: isDisabled ? textColor.withValues(alpha: 0.5) : textColor, letterSpacing: 0, fontWeight: fontWeight, ), From 16b5ff1b62358975b3d9c3b8229cbbe8eb6fd5db Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Sun, 16 Nov 2025 10:04:36 +0300 Subject: [PATCH 2/3] Ancillary Orders Flow Completed --- .../ancillary_order_list_response_model.dart | 8 +- .../todo_section/todo_section_repo.dart | 4 +- .../todo_section/todo_section_view_model.dart | 4 +- .../prescription_detail_page.dart | 17 +- .../ancillary_order_payment_page.dart | 172 +++++++++++++++++- .../ancillary_procedures_details_page.dart | 84 ++++++--- lib/presentation/todo_section/todo_page.dart | 6 +- .../widgets/ancillary_orders_list.dart | 78 ++++---- lib/widgets/buttons/custom_button.dart | 2 +- lib/widgets/common_bottom_sheet.dart | 51 +++--- 10 files changed, 316 insertions(+), 110 deletions(-) diff --git a/lib/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart b/lib/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart index bad8006..c014bee 100644 --- a/lib/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart +++ b/lib/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart @@ -76,6 +76,8 @@ class AncillaryOrderItem { bool? isQueued; DateTime? orderDate; int? orderNo; + String? projectName; // Added from parent AncillaryOrderGroup + int? projectID; // Added from parent AncillaryOrderGroup AncillaryOrderItem({ this.ancillaryProcedureListModels, @@ -90,9 +92,11 @@ class AncillaryOrderItem { this.isQueued, this.orderDate, this.orderNo, + this.projectName, + this.projectID, }); - factory AncillaryOrderItem.fromJson(Map json) => AncillaryOrderItem( + factory AncillaryOrderItem.fromJson(Map json, {String? projectName, int? projectID}) => AncillaryOrderItem( ancillaryProcedureListModels: json['AncillaryProcedureListModels'], appointmentDate: DateUtil.convertStringToDate(json['AppointmentDate']), appointmentNo: json['AppointmentNo'] as int?, @@ -105,5 +109,7 @@ class AncillaryOrderItem { isQueued: json['IsQueued'] as bool?, orderDate: DateUtil.convertStringToDate(json['OrderDate']), orderNo: json['OrderNo'] as int?, + projectName: projectName, + projectID: projectID, ); } diff --git a/lib/features/todo_section/todo_section_repo.dart b/lib/features/todo_section/todo_section_repo.dart index 60754e2..008b22c 100644 --- a/lib/features/todo_section/todo_section_repo.dart +++ b/lib/features/todo_section/todo_section_repo.dart @@ -81,11 +81,13 @@ class TodoSectionRepoImp implements TodoSectionRepo { for (var group in groupsList) { if (group is Map && group['AncillaryOrderList'] != null) { final ordersList = group['AncillaryOrderList'] as List; + final projectName = group['ProjectName'] as String?; + final projectID = group['ProjectID'] as int?; // Parse each order item in the group for (var orderJson in ordersList) { if (orderJson is Map) { - ancillaryOrders.add(AncillaryOrderItem.fromJson(orderJson)); + ancillaryOrders.add(AncillaryOrderItem.fromJson(orderJson, projectName: projectName, projectID: projectID)); } } } diff --git a/lib/features/todo_section/todo_section_view_model.dart b/lib/features/todo_section/todo_section_view_model.dart index 0d97828..c0fb96b 100644 --- a/lib/features/todo_section/todo_section_view_model.dart +++ b/lib/features/todo_section/todo_section_view_model.dart @@ -223,9 +223,7 @@ class TodoSectionViewModel extends ChangeNotifier { Function(dynamic)? onSuccess, Function(String)? onError, }) async { - final result = await todoSectionRepo.applePayInsertRequest( - applePayInsertRequest: applePayInsertRequest, - ); + final result = await todoSectionRepo.applePayInsertRequest(applePayInsertRequest: applePayInsertRequest); result.fold( (failure) async { diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index 473f79a..e0e78c2 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -13,15 +13,13 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.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/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_item_view.dart'; -import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_reminder_view.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/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'; @@ -127,7 +125,8 @@ class _PrescriptionDetailPageState extends State { children: [ AppCustomChipWidget( icon: AppAssets.doctor_calendar_icon, - labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), false), + labelText: DateUtil.formatDateToDate( + DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), false), labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), ), AppCustomChipWidget( @@ -214,18 +213,22 @@ class _PrescriptionDetailPageState extends State { hasShadow: true, ), child: CustomButton( - text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context), + text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! + ? LocaleKeys.resendOrder.tr(context: context) + : LocaleKeys.prescriptionDeliveryError.tr(context: context), onPressed: () {}, backgroundColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.greyF7Color, borderColor: AppColors.successColor.withOpacity(0.01), - textColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.whiteColor : AppColors.textColor.withOpacity(0.35), + textColor: + widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.whiteColor : AppColors.textColor.withOpacity(0.35), fontSize: 16, fontWeight: FontWeight.w500, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, icon: AppAssets.prescription_refill_icon, - iconColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.whiteColor : AppColors.textColor.withOpacity(0.35), + iconColor: + widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.whiteColor : AppColors.textColor.withOpacity(0.35), iconSize: 20.h, ).paddingSymmetrical(24.h, 24.h), ), diff --git a/lib/presentation/todo_section/ancillary_order_payment_page.dart b/lib/presentation/todo_section/ancillary_order_payment_page.dart index 65ab778..f9995dc 100644 --- a/lib/presentation/todo_section/ancillary_order_payment_page.dart +++ b/lib/presentation/todo_section/ancillary_order_payment_page.dart @@ -2,25 +2,35 @@ import 'dart:async'; import 'dart:developer'; import 'dart:io'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.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/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/date_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/features/payfort/models/apple_pay_request_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_procedures_detail_response_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/home/navigation_screen.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/in_app_browser/InAppBrowser.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'; class AncillaryOrderPaymentPage extends StatefulWidget { + final DateTime? appointmentDate; final int appointmentNoVida; final int orderNo; final int projectID; @@ -29,6 +39,7 @@ class AncillaryOrderPaymentPage extends StatefulWidget { const AncillaryOrderPaymentPage({ super.key, + required this.appointmentDate, required this.appointmentNoVida, required this.orderNo, required this.projectID, @@ -171,7 +182,7 @@ class _AncillaryOrderPaymentPageState extends State { ), // Payment Summary Footer - todoVM.isProcessingPayment ? SizedBox.shrink() : _buildPaymentSummary(), + todoVM.isProcessingPayment ? SizedBox.shrink() : _buildPaymentSummary() ], ); }, @@ -256,7 +267,7 @@ class _AncillaryOrderPaymentPageState extends State { fit: BoxFit.contain, ).paddingSymmetrical(24.h, 0.h).onPress(() { if (!todoSectionViewModel.isProcessingPayment) { - _openPaymentURL("ApplePay"); + _startApplePay(); } }) : SizedBox(height: 12.h), @@ -474,11 +485,160 @@ class _AncillaryOrderPaymentPageState extends State { // Show success message and navigate Utils.showToast("Payment successful! Invoice #: $invoiceNo"); - // Navigate back to home after a short delay - Future.delayed(Duration(seconds: 2), () { - Navigator.of(context).pop(); // Close payment page - Navigator.of(context).pop(); // Close details page + Future.delayed(Duration(seconds: 1), () { + showCommonBottomSheetWithoutHeight( + context, + child: Column( + children: [ + Row( + children: [ + "Here is your invoice #: ".needTranslation.toText14( + color: AppColors.textColorLight, + weight: FontWeight.w500, + ), + SizedBox(width: 4.w), + ("12345").toText16(isBold: true), + ], + ), + SizedBox(height: 24.h), + Row( + children: [ + Expanded( + child: CustomButton( + height: 56.h, + text: LocaleKeys.ok.tr(), + onPressed: () { + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + ), + ), + ], + ), + ], + ), + // title: "Payment Completed Successfully".needTranslation, + titleWidget: Utils.getSuccessWidget(loadingText: "Payment Completed Successfully".needTranslation), + isCloseButtonVisible: false, + isDismissible: false, + isFullScreen: false, + ); }); } + + _startApplePay() async { + showCommonBottomSheet( + context, + child: Utils.getLoadingWidget(), + callBackFunc: (str) {}, + title: "", + height: ResponsiveExtension.screenHeight * 0.3, + isCloseButtonVisible: false, + isDismissible: false, + isFullScreen: false, + ); + final user = appState.getAuthenticatedUser(); + transID = Utils.getAdvancePaymentTransID(widget.projectID, user!.patientId!); + + ApplePayInsertRequest applePayInsertRequest = ApplePayInsertRequest(); + await payfortViewModel.getPayfortConfigurations( + serviceId: ServiceTypeEnum.ancillaryOrder.getIdFromServiceEnum(), + projectId: widget.projectID, + integrationId: 2, + ); + + applePayInsertRequest.clientRequestID = transID; + applePayInsertRequest.clinicID = 0; + + applePayInsertRequest.currency = appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED"; + applePayInsertRequest.customerEmail = "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com"; + applePayInsertRequest.customerID = appState.getAuthenticatedUser()!.patientId.toString(); + applePayInsertRequest.customerName = "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}"; + + applePayInsertRequest.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken); + applePayInsertRequest.voipToken = await Utils.getStringFromPrefs(CacheConst.voipToken); + applePayInsertRequest.doctorID = 0; + applePayInsertRequest.projectID = widget.projectID.toString(); + applePayInsertRequest.serviceID = ServiceTypeEnum.ancillaryOrder.getIdFromServiceEnum().toString(); + applePayInsertRequest.channelID = 3; + applePayInsertRequest.patientID = appState.getAuthenticatedUser()!.patientId.toString(); + applePayInsertRequest.patientTypeID = appState.getAuthenticatedUser()!.patientType; + applePayInsertRequest.patientOutSA = appState.getAuthenticatedUser()!.outSa; + applePayInsertRequest.appointmentDate = DateUtil.convertDateToString(widget.appointmentDate ?? DateTime.now()); + applePayInsertRequest.appointmentNo = widget.appointmentNoVida; + applePayInsertRequest.orderDescription = "Ancillary Order Payment"; + applePayInsertRequest.liveServiceID = "0"; + applePayInsertRequest.latitude = "0.0"; + applePayInsertRequest.longitude = "0.0"; + applePayInsertRequest.amount = widget.totalAmount.toString(); + applePayInsertRequest.isSchedule = "0"; + applePayInsertRequest.language = appState.isArabic() ? 'ar' : 'en'; + applePayInsertRequest.languageID = appState.isArabic() ? 1 : 2; + applePayInsertRequest.userName = appState.getAuthenticatedUser()!.patientId; + applePayInsertRequest.responseContinueURL = "http://hmg.com/Documents/success.html"; + applePayInsertRequest.backClickUrl = "http://hmg.com/Documents/success.html"; + applePayInsertRequest.paymentOption = "ApplePay"; + + applePayInsertRequest.isMobSDK = true; + applePayInsertRequest.merchantReference = transID; + applePayInsertRequest.merchantIdentifier = payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier; + applePayInsertRequest.commandType = "PURCHASE"; + applePayInsertRequest.signature = payfortViewModel.payfortProjectDetailsRespModel!.signature; + applePayInsertRequest.accessCode = payfortViewModel.payfortProjectDetailsRespModel!.accessCode; + applePayInsertRequest.shaRequestPhrase = payfortViewModel.payfortProjectDetailsRespModel!.shaRequest; + applePayInsertRequest.shaResponsePhrase = payfortViewModel.payfortProjectDetailsRespModel!.shaResponse; + applePayInsertRequest.returnURL = ""; + + try { + await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest); + } catch (error) { + log("Apple Pay Insert Request Failed: $error"); + Navigator.of(context).pop(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Failed to initialize Apple Pay. Please try again.".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + return; + } + // Only proceed with Apple Pay if insert was successful + payfortViewModel.paymentWithApplePay( + customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", + customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", + orderDescription: "Ancillary Order Payment", + orderAmount: widget.totalAmount, + merchantReference: transID, + merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier, + applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode, + applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest, + currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", + onFailed: (failureResult) async { + log("failureResult: ${failureResult.message.toString()}"); + Navigator.of(context).pop(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + onSucceeded: (successResult) async { + Navigator.of(context).pop(); + log("successResult: ${successResult.responseMessage.toString()}"); + selectedPaymentMethod = successResult.paymentOption ?? "VISA"; + _checkPaymentStatus(); + }, + ); + } } diff --git a/lib/presentation/todo_section/ancillary_procedures_details_page.dart b/lib/presentation/todo_section/ancillary_procedures_details_page.dart index 71e7782..449d21e 100644 --- a/lib/presentation/todo_section/ancillary_procedures_details_page.dart +++ b/lib/presentation/todo_section/ancillary_procedures_details_page.dart @@ -23,23 +23,25 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; -class AncillaryProceduresDetailsList extends StatefulWidget { +class AncillaryOrderDetailsList extends StatefulWidget { final int appointmentNoVida; final int orderNo; final int projectID; + final String projectName; - const AncillaryProceduresDetailsList({ + const AncillaryOrderDetailsList({ super.key, required this.appointmentNoVida, required this.orderNo, required this.projectID, + required this.projectName, }); @override - State createState() => _AncillaryProceduresDetailsListState(); + State createState() => _AncillaryOrderDetailsListState(); } -class _AncillaryProceduresDetailsListState extends State { +class _AncillaryOrderDetailsListState extends State { late TodoSectionViewModel todoSectionViewModel; late AppState appState; List selectedProcedures = []; @@ -77,6 +79,7 @@ class _AncillaryProceduresDetailsListState extends State { onCheckIn: (order) => log("Check-in for order: ${order.orderNo}"), onViewDetails: (order) async { Navigator.of(context).push(CustomPageRoute( - page: AncillaryProceduresDetailsList( + page: AncillaryOrderDetailsList( appointmentNoVida: order.appointmentNo ?? 0, orderNo: order.orderNo ?? 0, - projectID: 15, - // TODO: NEED to Confirm about projectID + projectID: order.projectID ?? 0, + projectName: order.projectName ?? "", ))); log("View details for order: ${order.orderNo}"); }, diff --git a/lib/presentation/todo_section/widgets/ancillary_orders_list.dart b/lib/presentation/todo_section/widgets/ancillary_orders_list.dart index a1c99d9..31a778f 100644 --- a/lib/presentation/todo_section/widgets/ancillary_orders_list.dart +++ b/lib/presentation/todo_section/widgets/ancillary_orders_list.dart @@ -114,33 +114,42 @@ class AncillaryOrderCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header Row with Order Number and Date - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - if (!isLoading) - "Order #".needTranslation.toText14( - color: AppColors.textColorLight, - weight: FontWeight.w500, - ), - SizedBox(width: 4.w), - (isLoading ? "12345" : "${order.orderNo ?? '-'}").toText16(isBold: true).toShimmer2(isShow: isLoading), - ], - ), - if (order.orderDate != null || isLoading) - (isLoading ? "Jan 15, 2024" : DateFormat('MMM dd, yyyy').format(order.orderDate!)) - .toText12(color: AppColors.textColorLight) - .toShimmer2(isShow: isLoading), - ], - ), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Row( + // children: [ + // if (!isLoading) + // "Order #".needTranslation.toText14( + // color: AppColors.textColorLight, + // weight: FontWeight.w500, + // ), + // SizedBox(width: 4.w), + // (isLoading ? "12345" : "${order.orderNo ?? '-'}").toText16(isBold: true).toShimmer2(isShow: isLoading), + // ], + // ), + // if (order.orderDate != null || isLoading) + // (isLoading ? "Jan 15, 2024" : DateFormat('MMM dd, yyyy').format(order.orderDate!)) + // .toText12(color: AppColors.textColorLight) + // .toShimmer2(isShow: isLoading), + // ], + // ), SizedBox(height: 12.h), // Doctor and Clinic Info Row( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ + if (!isLoading) ...[ + Image.network( + "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown.png", + width: 40.w, + height: 40.h, + fit: BoxFit.cover, + ).circle(100.r), + SizedBox(width: 12.w), + ], Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -153,17 +162,6 @@ class AncillaryOrderCard extends StatelessWidget { .toShimmer2(isShow: isLoading), SizedBox(height: 4.h), - - // Clinic Name - if (order.clinicName != null || isLoading) - (isLoading ? "Cardiology Clinic" : order.clinicName!) - .toString() - .toText12( - fontWeight: FontWeight.w500, - color: AppColors.greyTextColor, - maxLine: 2, - ) - .toShimmer2(isShow: isLoading), ], ), ), @@ -178,6 +176,18 @@ class AncillaryOrderCard extends StatelessWidget { spacing: 3.h, runSpacing: 4.h, children: [ + // projectName + if (order.projectName != null || isLoading) + AppCustomChipWidget( + labelText: order.projectName ?? '-', + ).toShimmer2(isShow: isLoading), + // orderNo + if (order.orderNo != null || isLoading) + AppCustomChipWidget( + // icon: AppAssets.calendar, + labelText: "${"Order# :".needTranslation}${order.orderNo ?? '-'}", + ).toShimmer2(isShow: isLoading), + // Appointment Date if (order.appointmentDate != null || isLoading) AppCustomChipWidget( @@ -189,7 +199,7 @@ class AncillaryOrderCard extends StatelessWidget { // Appointment Number if (order.appointmentNo != null || isLoading) AppCustomChipWidget( - labelText: isLoading ? "Appt #: 98765" : "Appt #: ${order.appointmentNo}".needTranslation, + labelText: isLoading ? "Appt# : 98765" : "Appt #: ${order.appointmentNo}".needTranslation, ).toShimmer2(isShow: isLoading), // Invoice Number @@ -261,8 +271,6 @@ class AncillaryOrderCard extends StatelessWidget { borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), height: 40.h, - icon: AppAssets.arrow_forward, - iconColor: AppColors.primaryRedColor, iconSize: 15.h, ).toShimmer2(isShow: isLoading), ), diff --git a/lib/widgets/buttons/custom_button.dart b/lib/widgets/buttons/custom_button.dart index 03db819..b823eae 100644 --- a/lib/widgets/buttons/custom_button.dart +++ b/lib/widgets/buttons/custom_button.dart @@ -67,7 +67,7 @@ class CustomButton extends StatelessWidget { color: isDisabled ? backgroundColor.withValues(alpha: .5) : backgroundColor, borderRadius: radius, customBorder: BorderRadius.circular(radius), - side: borderSide ?? BorderSide(width: borderWidth.h, color: isDisabled ? borderColor.withValues(alpha: 0.5) : borderColor)), + side: borderSide ?? BorderSide(width: borderWidth.h, color: borderColor)), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index 4d0dcf1..1d857b5 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -105,15 +105,15 @@ class ButtonSheetContent extends StatelessWidget { } void showCommonBottomSheetWithoutHeight( - BuildContext context, { - required Widget child, - required VoidCallback callBackFunc, - String title = "", - bool isCloseButtonVisible = true, - bool isFullScreen = true, - bool isDismissible = true, - Widget? titleWidget, - bool useSafeArea = false, + BuildContext context, { + required Widget child, + VoidCallback? callBackFunc, + String title = "", + bool isCloseButtonVisible = true, + bool isFullScreen = true, + bool isDismissible = true, + Widget? titleWidget, + bool useSafeArea = false, bool hasBottomPadding = true, Color backgroundColor = AppColors.bottomSheetBgColor, }) { @@ -143,13 +143,12 @@ void showCommonBottomSheetWithoutHeight( ), child: SingleChildScrollView( physics: ClampingScrollPhysics(), - child: isCloseButtonVisible - ? Container( + child: Container( padding: EdgeInsets.only( - left: 24, - top: 24, - right: 24, - bottom: 12, + left: 24.w, + top: 24.h, + right: 24.w, + bottom: 12.h, ), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.bottomSheetBgColor, @@ -157,6 +156,7 @@ void showCommonBottomSheetWithoutHeight( ), child: Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -166,26 +166,29 @@ void showCommonBottomSheetWithoutHeight( Expanded( child: title.toText20(weight: FontWeight.w600), ), - Utils.buildSvgWithAssets( - icon: AppAssets.close_bottom_sheet_icon, - iconColor: Color(0xff2B353E), - ).onPress(() { - Navigator.of(context).pop(); - }), + if (isCloseButtonVisible) ...[ + Utils.buildSvgWithAssets( + icon: AppAssets.close_bottom_sheet_icon, + iconColor: Color(0xff2B353E), + ).onPress(() { + Navigator.of(context).pop(); + }), + ], ], ), SizedBox(height: 16.h), child, ], ), - ) - : child, + ), ), ), ); }, ).then((value) { - callBackFunc(); + if (callBackFunc != null) { + callBackFunc(); + } }); } From 7d64f782462cb429faebd93f4389588bde51b78f Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Sun, 16 Nov 2025 10:13:10 +0300 Subject: [PATCH 3/3] merge changes --- lib/core/api_consts.dart | 2 ++ .../emergency_services_repo.dart | 32 +++++++++++++------ .../emergency_services_view_model.dart | 2 +- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 9c56e6a..bdaba66 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -708,6 +708,8 @@ const SAVE_SETTING = 'Services/Patients.svc/REST/UpdatePateintInfo'; const DEACTIVATE_ACCOUNT = 'Services/Patients.svc/REST/PatientAppleActivation_InsertUpdate'; +var ER_CREATE_ADVANCE_PAYMENT = "services/Doctors.svc/REST/ER_CreateAdvancePaymentForClinic"; + //family Files const FAMILY_FILES = 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatus'; diff --git a/lib/features/emergency_services/emergency_services_repo.dart b/lib/features/emergency_services/emergency_services_repo.dart index d8ba717..3b70cbb 100644 --- a/lib/features/emergency_services/emergency_services_repo.dart +++ b/lib/features/emergency_services/emergency_services_repo.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:dartz/dartz.dart'; import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; @@ -22,10 +24,15 @@ abstract class EmergencyServicesRepo { Future>> checkPatientERPaymentInformation({int projectID}); - Future>> ER_CreateAdvancePayment( - {required int projectID, required AuthenticatedUser authUser, required num paymentAmount, required String paymentMethodName, required String paymentReference}); + Future>> createAdvancePaymentForER( + {required int projectID, + required AuthenticatedUser authUser, + required num paymentAmount, + required String paymentMethodName, + required String paymentReference}); - Future>> addAdvanceNumberRequest({required String advanceNumber, required String paymentReference, required String appointmentNo}); + Future>> addAdvanceNumberRequest( + {required String advanceNumber, required String paymentReference, required String appointmentNo}); Future>> getProjectIDFromNFC({required String nfcCode}); @@ -54,7 +61,8 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { try { final list = response['List_ProjectAvgERWaitingTime']; - final clinicsList = list.map((item) => ProjectAvgERWaitingTime.fromJson(item as Map)).toList().cast(); + final clinicsList = + list.map((item) => ProjectAvgERWaitingTime.fromJson(item as Map)).toList().cast(); apiResponse = GenericApiModel>( messageStatus: messageStatus, statusCode: statusCode, @@ -90,7 +98,8 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { final list = response['Vida_ProcedureList']; - final proceduresList = list.map((item) => RRTProceduresResponseModel.fromJson(item as Map)).toList().cast(); + final proceduresList = + list.map((item) => RRTProceduresResponseModel.fromJson(item as Map)).toList().cast(); apiResponse = GenericApiModel>( messageStatus: messageStatus, @@ -221,8 +230,12 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { } @override - Future> ER_CreateAdvancePayment( - {required int projectID, required AuthenticatedUser authUser, required num paymentAmount, required String paymentMethodName, required String paymentReference}) async { + Future> createAdvancePaymentForER( + {required int projectID, + required AuthenticatedUser authUser, + required num paymentAmount, + required String paymentMethodName, + required String paymentReference}) async { Map mapDevice = { "LanguageID": 1, "ERAdvanceAmount": { @@ -252,7 +265,7 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { final vidaAdvanceNumber = response['ER_AdvancePaymentResponse']['AdvanceNumber'].toString(); - print(vidaAdvanceNumber); + log(vidaAdvanceNumber); apiResponse = GenericApiModel( messageStatus: messageStatus, statusCode: statusCode, @@ -273,7 +286,8 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { } @override - Future> addAdvanceNumberRequest({required String advanceNumber, required String paymentReference, required String appointmentNo}) async { + Future> addAdvanceNumberRequest( + {required String advanceNumber, required String paymentReference, required String appointmentNo}) async { Map requestBody = { "AdvanceNumber": advanceNumber, "AdvanceNumber_VP": advanceNumber, diff --git a/lib/features/emergency_services/emergency_services_view_model.dart b/lib/features/emergency_services/emergency_services_view_model.dart index 812b79f..ead18f5 100644 --- a/lib/features/emergency_services/emergency_services_view_model.dart +++ b/lib/features/emergency_services/emergency_services_view_model.dart @@ -339,7 +339,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { } Future ER_CreateAdvancePayment({required String paymentMethodName, required String paymentReference, Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await emergencyServicesRepo.ER_CreateAdvancePayment( + final result = await emergencyServicesRepo.createAdvancePaymentForER( projectID: selectedHospital!.iD, authUser: appState.getAuthenticatedUser()!, paymentAmount: erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!,