diff --git a/assets/images/png/cc_ar.png b/assets/images/png/cc_ar.png new file mode 100644 index 00000000..e4388ba2 Binary files /dev/null and b/assets/images/png/cc_ar.png differ diff --git a/assets/images/png/cc_en.png b/assets/images/png/cc_en.png new file mode 100644 index 00000000..c11cf5e6 Binary files /dev/null and b/assets/images/png/cc_en.png differ diff --git a/assets/images/svg/all_payment_method.svg b/assets/images/svg/all_payment_method.svg new file mode 100644 index 00000000..ef72e6ac --- /dev/null +++ b/assets/images/svg/all_payment_method.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/svg/comprehensive_checkup.svg b/assets/images/svg/comprehensive_checkup.svg new file mode 100644 index 00000000..885d9a7d --- /dev/null +++ b/assets/images/svg/comprehensive_checkup.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/svg/e-referral.svg b/assets/images/svg/e-referral.svg new file mode 100644 index 00000000..3262779c --- /dev/null +++ b/assets/images/svg/e-referral.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/svg/ic_rrt_vehicle.svg b/assets/images/svg/ic_rrt_vehicle.svg new file mode 100644 index 00000000..d858fb4e --- /dev/null +++ b/assets/images/svg/ic_rrt_vehicle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/mada.svg b/assets/images/svg/mada.svg new file mode 100644 index 00000000..99fd1326 --- /dev/null +++ b/assets/images/svg/mada.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index b276dd7c..6a5d34f1 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Flutter import UIKit -import FirebaseCore -import FirebaseMessaging +//import FirebaseCore +//import FirebaseMessaging import GoogleMaps @main @objc class AppDelegate: FlutterAppDelegate { @@ -10,13 +10,13 @@ import GoogleMaps didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GMSServices.provideAPIKey("AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng") - FirebaseApp.configure() +// FirebaseApp.configure() GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken:Data){ - Messaging.messaging().apnsToken = deviceToken +// Messaging.messaging().apnsToken = deviceToken super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) } } diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 3d5f3379..aa534f6d 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -88,22 +88,22 @@ 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; } else { if (isRCService) { - url = RC_BASE_URL + endPoint; + url = ApiConsts.rcBaseUrl + endPoint; } else { url = ApiConsts.baseUrl + 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; @@ -160,7 +161,7 @@ class ApiClientImp implements ApiClient { // body['VersionID'] = ApiConsts.appVersionID.toString(); if (!isExternal) { - body['VersionID'] = "50.0"; + body['VersionID'] = ApiConsts.appVersionID.toString(); body['Channel'] = ApiConsts.appChannelId.toString(); body['IPAdress'] = ApiConsts.appIpAddress; body['generalid'] = ApiConsts.appGeneralId; @@ -174,6 +175,7 @@ class ApiClientImp implements ApiClient { } // body['TokenID'] = "@dm!n"; + // body['PatientID'] = 4772429; // body['PatientID'] = 1231755; // body['PatientTypeID'] = 1; // @@ -182,9 +184,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) { @@ -199,7 +202,10 @@ class ApiClientImp implements ApiClient { final response = await http.post(Uri.parse(url.trim()), body: json.encode(body), headers: headers); final int statusCode = response.statusCode; + log("uri: ${Uri.parse(url.trim())}"); + log("body: ${json.encode(body)}"); log("response.body: ${response.body}"); + // log("response.body: ${response.body}"); if (statusCode < 200 || statusCode >= 400) { onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data")); logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); @@ -210,35 +216,44 @@ 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 is Map && parsed.containsKey('MessageStatus')) ? parsed['MessageStatus'] : 1, + errorMessage: (parsed is Map && parsed.containsKey('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 is Map && parsed.containsKey('MessageStatus')) ? parsed['MessageStatus'] : 1, + errorMessage: (parsed is Map && parsed.containsKey('ErrorEndUserMessage')) ? parsed['ErrorEndUserMessage'] : ""); } 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 +263,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 +294,6 @@ class ApiClientImp implements ApiClient { logApiEndpointError(endPoint, parsed['ErrorSearchMsg'], statusCode); } } else { - onFailure( parsed['message'] ?? parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, @@ -287,7 +304,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( @@ -338,9 +356,9 @@ class ApiClientImp implements ApiClient { url = endPoint; } else { if (isRCService) { - url = RC_BASE_URL + endPoint; + url = ApiConsts.rcBaseUrl + endPoint; } else { - url = BASE_URL + endPoint; + url = ApiConsts.baseUrl + endPoint; } } if (queryParams != null) { @@ -351,7 +369,7 @@ class ApiClientImp implements ApiClient { debugPrint("URL : $url"); // print("Body : ${json.encode(body)}"); - if (await Utils.checkConnection()) { + if (await Utils.checkConnection(bypassConnectionCheck: true)) { final response = await http.get( Uri.parse(url.trim()), headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}, diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 29ec562f..a1f40629 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'; @@ -50,8 +46,6 @@ var PHARMACY_REDIRECT_URL = 'https://bit.ly/AlhabibPharmacy'; // RC API URL // var RC_BASE_URL = 'https://rc.hmg.com/'; -var RC_BASE_URL = 'https://rc.hmg.com/uat/'; - // var RC_BASE_URL = 'https://ms.hmg.com/rc/'; var PING_SERVICE = 'Services/Weather.svc/REST/CheckConnectivity'; @@ -265,7 +259,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 +268,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 +295,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'; @@ -534,12 +519,6 @@ var ADD_HHC_ORDER_RC = "api/HHC/add"; var GET_ALL_HHC_ORDERS_RC = 'api/hhc/list'; var UPDATE_HHC_ORDER_RC = 'api/hhc/update'; -// CMC RC SERVICES -var GET_ALL_CMC_SERVICES_RC = 'api/cmc/getallcmc'; -var ADD_CMC_ORDER_RC = 'api/cmc/add'; -var GET_ALL_CMC_ORDERS_RC = 'api/cmc/list'; -var UPDATE_CMC_ORDER_RC = 'api/cmc/update'; - // RRT RC SERVICES var ADD_RRT_ORDER_RC = "api/rrt/add"; var GET_ALL_RRT_ORDERS_RC = "api/rrt/list"; @@ -721,6 +700,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'; @@ -736,11 +717,7 @@ class ApiConsts { static String baseUrl = 'https://hmgwebservices.com/'; // HIS API URL PROD - 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 String rcBaseUrl = 'https://rc.hmg.com/'; // RC API URL PROD static var payFortEnvironment = FortEnvironment.production; static var applePayMerchantId = "merchant.com.hmgwebservices"; @@ -767,7 +744,7 @@ class ApiConsts { TAMARA_URL = "https://mdlaboratories.com/tamaralive/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid='; - RCBaseUrl = 'https://rc.hmg.com/'; + rcBaseUrl = 'https://rc.hmg.com/'; break; case AppEnvironmentTypeEnum.dev: baseUrl = "https://uat.hmgwebservices.com/"; @@ -777,7 +754,7 @@ class ApiConsts { TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; - RCBaseUrl = 'https://rc.hmg.com/test/'; + rcBaseUrl = 'https://rc.hmg.com/uat/'; break; case AppEnvironmentTypeEnum.uat: baseUrl = "https://uat.hmgwebservices.com/"; @@ -787,7 +764,7 @@ class ApiConsts { TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; - RCBaseUrl = 'https://rc.hmg.com/test/'; + rcBaseUrl = 'https://rc.hmg.com/uat/'; break; case AppEnvironmentTypeEnum.preProd: baseUrl = "https://webservices.hmg.com/"; @@ -797,7 +774,7 @@ class ApiConsts { TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; - RCBaseUrl = 'https://rc.hmg.com/'; + rcBaseUrl = 'https://rc.hmg.com/'; break; case AppEnvironmentTypeEnum.qa: baseUrl = "https://uat.hmgwebservices.com/"; @@ -807,7 +784,7 @@ class ApiConsts { TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; - RCBaseUrl = 'https://rc.hmg.com/test/'; + rcBaseUrl = 'https://rc.hmg.com/uat/'; break; case AppEnvironmentTypeEnum.staging: baseUrl = "https://uat.hmgwebservices.com/"; @@ -817,7 +794,7 @@ class ApiConsts { TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; - RCBaseUrl = 'https://rc.hmg.com/test/'; + rcBaseUrl = 'https://rc.hmg.com/uat/'; break; } } @@ -849,8 +826,33 @@ 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 - static final double appVersionID = 18.7; + // 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'; + + // RC COMPREHENSIVE MEDICAL CHECKUP ServIces + static final String allCMCOrdersRc = 'api/cmc/list'; + static final String allCMCServicesRc = 'api/cmc/getallcmc'; + static final String updateCMCOrder = 'api/cmc/update'; + static final String addCMCOrder = 'api/cmc/add'; + static final String getHospitalsList = 'Services/Lists.svc/REST/GetProject'; + + // RC HOME HEALTHCARE ServIces + static final String allHHCOrdersRc = 'api/hhc/list'; + static final String allHHCServicesRc = 'api/HHC/getallhhc'; + static final String updateHHCOrder = 'api/hhc/update'; + static final String addHHCOrder = 'api/HHC/add'; + + // ************ static values for Api **************** + static final double appVersionID = 19.3; static final int appChannelId = 3; static final String appIpAddress = "10.20.10.20"; static final String appGeneralId = "Cs2020@2016\$2958"; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index e8215ba2..5fccc6e7 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -171,6 +171,10 @@ class AppAssets { static const String to_arrow = '$svgBasePath/to_arrow.svg'; static const String dual_arrow = '$svgBasePath/to_arrow.svg'; static const String forward_arrow_medium = '$svgBasePath/forward_arrow_medium.svg'; + static const String eReferral = '$svgBasePath/e-referral.svg'; + static const String comprehensiveCheckup = '$svgBasePath/comprehensive_checkup.svg'; + static const String all_payment_method = '$svgBasePath/all_payment_method.svg'; + static const String ic_rrt_vehicle = '$svgBasePath/ic_rrt_vehicle.svg'; //bottom navigation// @@ -200,6 +204,8 @@ class AppAssets { static const String visa = '$pngBasePath/visa.png'; static const String lockIcon = '$pngBasePath/lock-icon.png'; static const String dummy_user = '$pngBasePath/dummy_user.png'; + static const String comprehensiveCheckupEn = '$pngBasePath/cc_en.png'; + static const String comprehensiveCheckupAr = '$pngBasePath/cc_er.png'; } class AppAnimations { diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index cc9d88d2..6c452476 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -15,6 +15,8 @@ import 'package:hmg_patient_app_new/features/emergency_services/emergency_servic import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_repo.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_repo.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_repo.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart'; @@ -35,6 +37,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'; @@ -44,7 +48,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'; @@ -102,46 +105,37 @@ 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( - () => LocationRepoImpl(apiClient: getIt())); + getIt.registerLazySingleton(() => TodoSectionRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => LocationRepoImpl(apiClient: getIt())); getIt.registerLazySingleton(() => ContactUsRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => HmgServicesRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton - getIt.registerLazySingleton( - () => LabViewModel(labRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()), - ); + getIt.registerLazySingleton(() => LabViewModel(labRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt())); getIt.registerLazySingleton( () => RadiologyViewModel( radiologyRepo: getIt(), errorHandlerService: getIt(), + navigationService: getIt() ), ); - getIt.registerLazySingleton( - () => PrescriptionsViewModel( - prescriptionsRepo: getIt(), - errorHandlerService: getIt(), - ), - ); + getIt.registerLazySingleton(() => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); - getIt.registerLazySingleton( - () => InsuranceViewModel( - insuranceRepo: getIt(), - errorHandlerService: getIt(), - ), - ); + getIt.registerLazySingleton(() => InsuranceViewModel(insuranceRepo: getIt(), errorHandlerService: getIt())); getIt.registerLazySingleton( - () => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()), - ); + () => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt())); getIt.registerLazySingleton( () => PayfortViewModel( @@ -165,7 +159,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( @@ -179,51 +179,49 @@ 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()); - getIt.registerLazySingleton( - () => DateRangeSelectorRangeViewModel(), - ); + getIt.registerLazySingleton(() => DateRangeSelectorRangeViewModel()); - getIt.registerLazySingleton( - () => DoctorFilterViewModel(), - ); + getIt.registerLazySingleton(() => DoctorFilterViewModel()); getIt.registerLazySingleton( - () => - AppointmentViaRegionViewmodel( - navigationService: getIt(), - appState: getIt(), - ), + () => AppointmentViaRegionViewmodel(navigationService: getIt(), appState: getIt()), ); getIt.registerLazySingleton( () => EmergencyServicesViewModel( - locationUtils: getIt(), - navServices: getIt(), - emergencyServicesRepo: getIt(), - appState: getIt(), - errorHandlerService: getIt(), - appointmentRepo: getIt(), - dialogService: getIt() - ), + locationUtils: getIt(), + navServices: getIt(), + emergencyServicesRepo: getIt(), + appState: getIt(), + errorHandlerService: getIt(), + appointmentRepo: getIt(), + dialogService: getIt()), ); getIt.registerLazySingleton( - () => LocationViewModel( - locationRepo: getIt(), - errorHandlerService: getIt(), - ), + () => LocationViewModel(locationRepo: getIt(), errorHandlerService: getIt()), ); getIt.registerLazySingleton( - () => ContactUsViewModel( - contactUsRepo: getIt(), - appState: getIt(), - errorHandlerService: getIt(), - ), + () => ContactUsViewModel(contactUsRepo: getIt(), appState: getIt(), errorHandlerService: getIt()), + ); + + getIt.registerLazySingleton( + () => TodoSectionViewModel(todoSectionRepo: getIt(), errorHandlerService: getIt()), + ); + + getIt.registerLazySingleton( + () => HmgServicesViewModel(bookAppointmentsRepo: getIt(), hmgServicesRepo: getIt(), errorHandlerService: getIt()), ); // Screen-specific VMs → Factory diff --git a/lib/core/utils/date_util.dart b/lib/core/utils/date_util.dart index d58aef60..e6856dbc 100644 --- a/lib/core/utils/date_util.dart +++ b/lib/core/utils/date_util.dart @@ -6,19 +6,19 @@ class DateUtil { /// convert String To Date function /// [date] String we want to convert static DateTime convertStringToDate(String? date) { - print("the date is $date"); + if (date == null) return DateTime.now(); if (date.isEmpty) return DateTime.now(); - const start = "/Date("; - const end = "+0300)"; - final startIndex = date.indexOf(start); - final endIndex = date.indexOf(end, startIndex + start.length); - return DateTime.fromMillisecondsSinceEpoch(int.parse( - date.substring(startIndex + start.length, endIndex), - )); - + const start = "/Date("; + const end = "+0300)"; + final startIndex = date.indexOf(start); + final endIndex = date.indexOf(end, startIndex + start.length); + return DateTime.fromMillisecondsSinceEpoch(int.parse( + date.substring(startIndex + start.length, endIndex), + )) + ; } static DateTime convertStringToDateSaudiTimezone(String date, int projectId) { @@ -36,10 +36,10 @@ class DateUtil { // .add(Duration(hours: 4)); // } else { return DateTime.fromMillisecondsSinceEpoch( - int.parse( - date.substring(startIndex + start.length, endIndex), - ), - isUtc: true) + int.parse( + date.substring(startIndex + start.length, endIndex), + ), + isUtc: true) .add(Duration(hours: 3)); // } } else { @@ -156,7 +156,13 @@ class DateUtil { static String getDateFormatted(String date) { DateTime dateObj = DateUtil.convertStringToDate(date); - return DateUtil.getWeekDay(dateObj.weekday) + ", " + dateObj.day.toString() + " " + DateUtil.getMonth(dateObj.month) + " " + dateObj.year.toString(); + return DateUtil.getWeekDay(dateObj.weekday) + + ", " + + dateObj.day.toString() + + " " + + DateUtil.getMonth(dateObj.month) + + " " + + dateObj.year.toString(); } static String getISODateFormat(DateTime dateTime) { @@ -352,12 +358,41 @@ class DateUtil { if (dateTime != null) { return lang == 'en' ? getWeekDayEnglish(dateTime.weekday) + ", " + getMonth(dateTime.month) + " " + dateTime.day.toString() + " " + dateTime.year.toString() - : getWeekDayArabic(dateTime.weekday) + ", " + dateTime.day.toString() + " " + getMonthArabic(dateTime.month) + " " + dateTime.year.toString(); + : getWeekDayArabic(dateTime.weekday) + + ", " + + dateTime.day.toString() + + " " + + getMonthArabic(dateTime.month) + + " " + + dateTime.year.toString(); } else { return ""; } } + static String getDateStringForNearestSlot(String date) { + DateTime dateObj = DateUtil.convertStringToDate(date); + return DateUtil.getWeekDay(dateObj.weekday) + + ", " + + dateObj.day.toString() + + " " + + DateUtil.getMonth(dateObj.month) + + " " + + dateObj.year.toString() + + " " + + dateObj.hour.toString() + + ":" + + getMinute(dateObj); + } + + static String getMinute(DateTime dateObj) { + if (dateObj.minute == 0) { + return dateObj.minute.toString() + "0"; + } else { + return dateObj.minute.toString(); + } + } + static String getMonthDayYearLangDateFormatted(DateTime dateTime, String lang) { if (dateTime != null) { return lang == 'en' @@ -381,7 +416,9 @@ class DateUtil { static String getMonthYearLangDateFormatted(DateTime dateTime, String lang) { if (dateTime != null) { - return lang == 'en' ? getMonth(dateTime.month) + " " + dateTime.year.toString() : getMonthArabic(dateTime.month) + " " + dateTime.year.toString(); + return lang == 'en' + ? getMonth(dateTime.month) + " " + dateTime.year.toString() + : getMonthArabic(dateTime.month) + " " + dateTime.year.toString(); } else { return ""; } @@ -488,10 +525,8 @@ class DateUtil { } } - -extension OnlyDate on DateTime{ - - DateTime provideDateOnly(){ +extension OnlyDate on DateTime { + DateTime provideDateOnly() { return DateTime(this.year, month, day); } -} \ No newline at end of file +} diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index a4ea9365..e57039cb 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 c05fe8bb..e3b108fc 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -102,8 +102,9 @@ class Utils { ? getMonthArabic(dateTime.month) + " " + dateTime.day.toString() + ", " + dateTime.year.toString() : getMonth(dateTime.month) + " " + dateTime.day.toString() + ", " + dateTime.year.toString(); } + static String getDayMonthYearDateFormatted(DateTime? dateTime) { - if(dateTime == null ) return ""; + if (dateTime == null) return ""; return appState.isArabic() ? "${dateTime.day.toString()} ${getMonthArabic(dateTime.month)}, ${dateTime.year.toString()}" : "${dateTime.day.toString()} ${getMonth(dateTime.month)}, ${dateTime.year.toString()}"; @@ -323,7 +324,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) @@ -339,7 +341,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), @@ -365,7 +368,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), @@ -373,14 +377,21 @@ 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: 100.h, height: 100.h, fit: BoxFit.fill), SizedBox(height: 8.h), - (loadingText ?? LocaleKeys.loadingText.tr()).toText14(color: AppColors.blackColor, letterSpacing: 0), + (loadingText ?? LocaleKeys.loadingText.tr()).toText15(color: AppColors.blackColor, letterSpacing: 0), SizedBox(height: 16.h), bodyWidget ?? SizedBox.shrink(), SizedBox(height: 16.h), @@ -698,7 +709,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); }, ); @@ -748,14 +759,15 @@ class Utils { ); } - static Widget getPaymentAmountWithSymbol2(num habibWalletAmount, - {double iconSize = 14, + static Widget getPaymentAmountWithSymbol2( + num habibWalletAmount, { + double iconSize = 14, double? fontSize, double? letterSpacing, FontWeight? fontWeight, Color iconColor = AppColors.textColor, - Color textColor = AppColors.blackColor, - bool isSaudiCurrency = true, + Color textColor = AppColors.blackColor, + bool isSaudiCurrency = true, bool isExpanded = true, }) { return RichText( @@ -772,7 +784,7 @@ class Utils { style: TextStyle( color: textColor, fontSize: fontSize ?? 32.f, - letterSpacing: letterSpacing??-4, + letterSpacing: letterSpacing ?? -4, fontWeight: fontWeight ?? FontWeight.w600, height: 1), ), @@ -816,7 +828,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; } @@ -834,7 +846,12 @@ class Utils { static PatientDoctorAppointmentList? convertToPatientDoctorAppointmentList(HospitalsModel? hospital) { if (hospital == null) return null; return PatientDoctorAppointmentList( - filterName: hospital.name, distanceInKMs: hospital.distanceInKilometers?.toString(), projectTopName: hospital.name, projectBottomName: hospital.name, model: hospital, isHMC: hospital.isHMC); + filterName: hospital.name, + distanceInKMs: hospital.distanceInKilometers?.toString(), + projectTopName: hospital.name, + projectBottomName: hospital.name, + model: hospital, + isHMC: hospital.isHMC); } static bool havePrivilege(int id) { @@ -848,7 +865,4 @@ class Utils { } return isHavePrivilege; } - - - } diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 1a6d1ccf..250453df 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -41,12 +41,14 @@ extension EmailValidator on String { FontWeight? weight, bool isBold = false, bool isUnderLine = false, + bool isCenter = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow, double letterSpacing = 0}) => Text( this, + textAlign: isCenter ? TextAlign.center : null, maxLines: maxlines, overflow: textOverflow, style: TextStyle( @@ -223,6 +225,7 @@ extension EmailValidator on String { FontWeight? weight, TextOverflow? textOverflow, double? letterSpacing = -0.4, + Color decorationColor =AppColors.errorColor }) => Text( this, @@ -236,6 +239,7 @@ extension EmailValidator on String { overflow: textOverflow, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), decoration: isUnderLine ? TextDecoration.underline : null, + decorationColor: decorationColor ), ); diff --git a/lib/extensions/widget_extensions.dart b/lib/extensions/widget_extensions.dart index 424aa882..70f10bbf 100644 --- a/lib/extensions/widget_extensions.dart +++ b/lib/extensions/widget_extensions.dart @@ -1,9 +1,8 @@ -import 'package:hmg_patient_app_new/core/enums.dart'; -import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:shimmer/shimmer.dart'; import 'package:sizer/sizer.dart'; import 'package:smooth_corner/smooth_corner.dart'; @@ -19,7 +18,8 @@ extension WidgetExtensions on Widget { Widget paddingAll(double _value) => Padding(padding: EdgeInsets.all(_value), child: this); - Widget paddingSymmetrical(double horizontal, double vertical) => Padding(padding: EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical), child: this); + Widget paddingSymmetrical(double horizontal, double vertical) => + Padding(padding: EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical), child: this); Widget paddingOnly({double left = 0.0, double right = 0.0, double top = 0.0, double bottom = 0.0}) => Padding(padding: EdgeInsetsDirectional.only(start: left, end: right, top: top, bottom: bottom), child: this); @@ -99,7 +99,7 @@ extension WidgetExtensions on Widget { bool disablePadding = false, double radius = 20, Color? color, - Color borderColor = AppColors.buttonColor, + Color? borderColor, bool disableWidth = false, bool isAlignment = false}) { return Container( @@ -110,7 +110,7 @@ extension WidgetExtensions on Widget { ), color: color, border: Border.all( - color: borderColor, + color: borderColor ?? Colors.transparent, width: disableWidth ? 2 : 1, ), ), diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index bcdacac6..3260ea57 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -339,7 +339,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/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index f24766bc..5d5fc713 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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/location_util.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; @@ -105,8 +104,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { List searchedRegionList = []; List facilityList = ["hmgHospitals", "hmcMedicalClinic"]; List searchedHospitalList = []; - List - searchedPatientDoctorAppointmentHospitalsList = []; + List searchedPatientDoctorAppointmentHospitalsList = []; List searchedClinicList = []; PatientDoctorAppointmentList? selectedHospitalForFilters; @@ -114,15 +112,14 @@ class BookAppointmentsViewModel extends ChangeNotifier { String? selectedClinicForFilters; bool applyFilters = false; - ///variables for laser clinic - List femaleLaserCategory = [ + List femaleLaserCategory = [ LaserCategoryType(1, 'bodyString'), LaserCategoryType(2, 'face'), - LaserCategoryType(10,'bikini'), + LaserCategoryType(10, 'bikini'), LaserCategoryType(11, 'retouch'), ]; - List maleLaserCategory =[ + List maleLaserCategory = [ LaserCategoryType(1, 'body'), LaserCategoryType(2, 'face'), LaserCategoryType(11, 'retouch'), @@ -136,9 +133,13 @@ class BookAppointmentsViewModel extends ChangeNotifier { bool isBodyPartsLoading = false; int duration = 0; - BookAppointmentsViewModel( - {required this.bookAppointmentsRepo, required this.errorHandlerService, required this.navigationService, required this.myAppointmentsViewModel, required this.locationUtils, required this.dialogService }) { + {required this.bookAppointmentsRepo, + required this.errorHandlerService, + required this.navigationService, + required this.myAppointmentsViewModel, + required this.locationUtils, + required this.dialogService}) { initBookAppointmentViewModel(); } @@ -287,7 +288,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { Future getLiveCareScheduleClinics({Function(dynamic)? onSuccess, Function(String)? onError}) async { liveCareClinicsList.clear(); - final result = await bookAppointmentsRepo.getLiveCareScheduleClinics(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!); + final result = + await bookAppointmentsRepo.getLiveCareScheduleClinics(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!); result.fold( (failure) async => await errorHandlerService.handleError(failure: failure), @@ -309,8 +311,9 @@ class BookAppointmentsViewModel extends ChangeNotifier { Future getLiveCareDoctorsList({Function(dynamic)? onSuccess, Function(String)? onError}) async { doctorsList.clear(); - final result = - await bookAppointmentsRepo.getLiveCareDoctorsList(selectedLiveCareClinic.serviceID!, _appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!, onError: onError); + final result = await bookAppointmentsRepo.getLiveCareDoctorsList( + selectedLiveCareClinic.serviceID!, _appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!, + onError: onError); result.fold( (failure) async { @@ -333,10 +336,15 @@ class BookAppointmentsViewModel extends ChangeNotifier { } //TODO: Make the API dynamic with parameters for ProjectID, isNearest, languageID, doctorId, doctorName - Future getDoctorsList({int projectID = 0, bool isNearest = false, int doctorId = 0, String doctorName = "", Function(dynamic)? onSuccess, Function(String)? onError}) async { + Future getDoctorsList( + {int projectID = 0, bool isNearest = true, int doctorId = 0, + String doctorName = "", + Function(dynamic)? onSuccess, + Function(String)? onError}) async { doctorsList.clear(); projectID = currentlySelectedHospitalFromRegionFlow != null ? int.parse(currentlySelectedHospitalFromRegionFlow!) : projectID; - final result = await bookAppointmentsRepo.getDoctorsList(selectedClinic.clinicID ?? 0, projectID, isNearest, doctorId, doctorName, isContinueDentalPlan: isContinueDentalPlan); + final result = + await bookAppointmentsRepo.getDoctorsList(selectedClinic.clinicID ?? 0, projectID, doctorName.isNotEmpty ? false : isNearest, doctorId, doctorName, isContinueDentalPlan: isContinueDentalPlan); result.fold( (failure) async { @@ -365,7 +373,13 @@ class BookAppointmentsViewModel extends ChangeNotifier { } Future getMappedDoctors( - {int projectID = 0, bool isNearest = false, int doctorId = 0, String doctorName = "", isContinueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { + {int projectID = 0, + bool isNearest = false, + int doctorId = 0, + String doctorName = "", + isContinueDentalPlan = false, + Function(dynamic)? onSuccess, + Function(String)? onError}) async { filteredHospitalList = null; hospitalList = null; isRegionListLoading = true; @@ -374,10 +388,10 @@ class BookAppointmentsViewModel extends ChangeNotifier { final result = await bookAppointmentsRepo.getDoctorsList(selectedClinic.clinicID ?? 0, projectID, isNearest, doctorId, doctorName); result.fold( - (failure) async { + (failure) async { onError?.call("No doctors found for the search criteria".needTranslation); }, - (apiResponse) async { + (apiResponse) async { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { @@ -401,7 +415,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { } Future getDoctorProfile({Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await bookAppointmentsRepo.getDoctorProfile(selectedDoctor.clinicID ?? 0, selectedDoctor.projectID ?? 0, selectedDoctor.doctorID ?? 0, onError: onError); + final result = await bookAppointmentsRepo + .getDoctorProfile(selectedDoctor.clinicID ?? 0, selectedDoctor.projectID ?? 0, selectedDoctor.doctorID ?? 0, onError: onError); result.fold( (failure) async {}, @@ -457,7 +472,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { // : date = DateUtil.convertStringToDateSaudiTimezone(element, int.parse(selectedDoctor.projectID.toString())); slotsList.add(FreeSlot(date, ['slot'])); - docFreeSlots.add(TimeSlot(isoTime: formatter.format(date), start: new DateTime(date.year, date.month, date.day, 0, 0, 0, 0), end: date, vidaDate: element)); + docFreeSlots.add(TimeSlot( + isoTime: formatter.format(date), start: new DateTime(date.year, date.month, date.day, 0, 0, 0, 0), end: date, vidaDate: element)); }); notifyListeners(); @@ -476,8 +492,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { final DateFormat dateFormatter = DateFormat('yyyy-MM-dd'); Map _eventsParsed; - final result = await bookAppointmentsRepo.getLiveCareDoctorFreeSlots( - selectedDoctor.clinicID ?? 0, selectedLiveCareClinic.serviceID ?? 0, selectedDoctor.projectID ?? 0, selectedDoctor.doctorID ?? 0, isBookingForLiveCare, + final result = await bookAppointmentsRepo.getLiveCareDoctorFreeSlots(selectedDoctor.clinicID ?? 0, selectedLiveCareClinic.serviceID ?? 0, + selectedDoctor.projectID ?? 0, selectedDoctor.doctorID ?? 0, isBookingForLiveCare, onError: onError); result.fold( @@ -501,7 +517,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { // : date = DateUtil.convertStringToDateSaudiTimezone(element, int.parse(selectedDoctor.projectID.toString())); slotsList.add(FreeSlot(date, ['slot'])); - docFreeSlots.add(TimeSlot(isoTime: formatter.format(date), start: new DateTime(date.year, date.month, date.day, 0, 0, 0, 0), end: date, vidaDate: element)); + docFreeSlots.add(TimeSlot( + isoTime: formatter.format(date), start: new DateTime(date.year, date.month, date.day, 0, 0, 0, 0), end: date, vidaDate: element)); }); notifyListeners(); @@ -513,7 +530,10 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); } - Future cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { + Future cancelAppointment( + {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, + Function(dynamic)? onSuccess, + Function(String)? onError}) async { final result = await bookAppointmentsRepo.cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel); result.fold( @@ -597,13 +617,15 @@ class BookAppointmentsViewModel extends ChangeNotifier { await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async { navigationService.pop(); Future.delayed(Duration(milliseconds: 50)).then((value) async {}); - LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: "Booking your appointment...".needTranslation); + LoadingUtils.showFullScreenLoader( + barrierDismissible: true, isSuccessDialog: false, loadingText: "Booking your appointment...".needTranslation); await insertSpecificAppointment( onError: (err) {}, onSuccess: (apiResp) async { LoadingUtils.hideFullScreenLoader(); await Future.delayed(Duration(milliseconds: 50)).then((value) async { - LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr()); + LoadingUtils.showFullScreenLoader( + barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr()); await Future.delayed(Duration(milliseconds: 4000)).then((value) { LoadingUtils.hideFullScreenLoader(); Navigator.pushAndRemoveUntil( @@ -693,13 +715,15 @@ class BookAppointmentsViewModel extends ChangeNotifier { await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async { navigationService.pop(); Future.delayed(Duration(milliseconds: 50)).then((value) async {}); - LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: "Booking your appointment...".needTranslation); + LoadingUtils.showFullScreenLoader( + barrierDismissible: true, isSuccessDialog: false, loadingText: "Booking your appointment...".needTranslation); await insertSpecificAppointment( onError: (err) {}, onSuccess: (apiResp) async { LoadingUtils.hideFullScreenLoader(); await Future.delayed(Duration(milliseconds: 50)).then((value) async { - LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr()); + LoadingUtils.showFullScreenLoader( + barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr()); await Future.delayed(Duration(milliseconds: 4000)).then((value) { LoadingUtils.hideFullScreenLoader(); Navigator.pushAndRemoveUntil( @@ -773,7 +797,9 @@ class BookAppointmentsViewModel extends ChangeNotifier { } else { filteredHospitalList = RegionList(); - var list = isHMG ? hospitalList?.registeredDoctorMap![selectedRegionId]!.hmgDoctorList : hospitalList?.registeredDoctorMap![selectedRegionId]!.hmcDoctorList; + var list = isHMG + ? hospitalList?.registeredDoctorMap![selectedRegionId]!.hmgDoctorList + : hospitalList?.registeredDoctorMap![selectedRegionId]!.hmcDoctorList; if (list != null && list.isEmpty) { notifyListeners(); @@ -856,12 +882,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } - void setSelections( - List? selectedFacilityForFilters, - List? selectedRegionForFilters, - String? selectedClinicForFilters, - PatientDoctorAppointmentList? selectedHospitalForFilters, - bool applyFilters) { + void setSelections(List? selectedFacilityForFilters, List? selectedRegionForFilters, String? selectedClinicForFilters, + PatientDoctorAppointmentList? selectedHospitalForFilters, bool applyFilters) { this.selectedFacilityForFilters = selectedFacilityForFilters; this.selectedClinicForFilters = selectedClinicForFilters; this.selectedHospitalForFilters = selectedHospitalForFilters; @@ -872,17 +894,14 @@ class BookAppointmentsViewModel extends ChangeNotifier { void getFiltersFromDoctorList() { doctorsList.forEach((element) { - if (!searchedRegionList - .contains(element.getRegionName(_appState.isArabic()))) { - searchedRegionList - .add(element.getRegionName(_appState.isArabic()) ?? ""); + if (!searchedRegionList.contains(element.getRegionName(_appState.isArabic()))) { + searchedRegionList.add(element.getRegionName(_appState.isArabic()) ?? ""); } if (!searchedHospitalList.contains(element.projectName)) { - searchedPatientDoctorAppointmentHospitalsList - .add(PatientDoctorAppointmentList() - ..filterName = element.projectName - ..isHMC = element.isHMC - ..distanceInKMs = "0"); + searchedPatientDoctorAppointmentHospitalsList.add(PatientDoctorAppointmentList() + ..filterName = element.projectName + ..isHMC = element.isHMC + ..distanceInKMs = "0"); searchedHospitalList.add(element.projectName ?? ""); } if (!searchedClinicList.contains(element.clinicName)) { @@ -939,27 +958,15 @@ class BookAppointmentsViewModel extends ChangeNotifier { return doctorsList; } var list = doctorsList.where((element) { - var isInSelectedRegion = (selectedRegionForFilters?.isEmpty == true) - ? true - : selectedRegionForFilters - ?.any((region) => region == element.getRegionName(isArabic())); - var shouldApplyFacilityFilter = - (selectedFacilityForFilters?.isEmpty == true) ? false : true; - var isHMC = (selectedFacilityForFilters?.isEmpty == true) - ? true - : selectedFacilityForFilters?.any((item) => item.contains("hmc")); - var isInSelectedClinic = (selectedClinicForFilters == null) - ? true - : selectedClinicForFilters == element.clinicName; - var isInSelectedHospital = (selectedHospitalForFilters == null) - ? true - : element.projectName == selectedHospitalForFilters?.filterName; + var isInSelectedRegion = + (selectedRegionForFilters?.isEmpty == true) ? true : selectedRegionForFilters?.any((region) => region == element.getRegionName(isArabic())); + var shouldApplyFacilityFilter = (selectedFacilityForFilters?.isEmpty == true) ? false : true; + var isHMC = (selectedFacilityForFilters?.isEmpty == true) ? true : selectedFacilityForFilters?.any((item) => item.contains("hmc")); + var isInSelectedClinic = (selectedClinicForFilters == null) ? true : selectedClinicForFilters == element.clinicName; + var isInSelectedHospital = (selectedHospitalForFilters == null) ? true : element.projectName == selectedHospitalForFilters?.filterName; var facilityFilter = ((shouldApplyFacilityFilter == true) ? isHMC : true); - return (isInSelectedRegion ?? true) && - (facilityFilter ?? true) && - isInSelectedClinic && - isInSelectedHospital; + return (isInSelectedRegion ?? true) && (facilityFilter ?? true) && isInSelectedClinic && isInSelectedHospital; }).toList(); return list; } @@ -1003,7 +1010,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { dentalChiefComplaintsList.clear(); notifyListeners(); int patientID = _appState.isAuthenticated ? _appState.getAuthenticatedUser()!.patientId ?? -1 : -1; - final result = await bookAppointmentsRepo.getDentalChiefComplaintsList(patientID: patientID, projectID: int.parse(currentlySelectedHospitalFromRegionFlow ?? "0"), clinicID: 17); + final result = await bookAppointmentsRepo.getDentalChiefComplaintsList( + patientID: patientID, projectID: int.parse(currentlySelectedHospitalFromRegionFlow ?? "0"), clinicID: 17); result.fold( (failure) async => await errorHandlerService.handleError(failure: failure), @@ -1051,7 +1059,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); } - setBodyType(int bodyType){ + setBodyType(int bodyType) { selectedBodyTypeIndex = bodyType; selectedCategory = 0; selectedBodyPartList = []; @@ -1059,33 +1067,33 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } - FutureOr getLaserClinic() async{ + FutureOr getLaserClinic() async { isBodyPartsLoading = true; int id = bodyTypes[selectedBodyTypeIndex][selectedCategory].laserCategoryID; int projectID = currentlySelectedHospitalFromRegionFlow != null ? int.parse(currentlySelectedHospitalFromRegionFlow!) : 0; int languageID = _appState.isArabic() ? 1 : 0; final result = await bookAppointmentsRepo.getLaserClinics(id, projectID, languageID); result.fold( - (failure) { + (failure) { isBodyPartsLoading = false; notifyListeners(); }, - (apiResponse) {isBodyPartsLoading = false; + (apiResponse) { + isBodyPartsLoading = false; if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { - List response =apiResponse.data!; - if(response.first.category == 2 || response.first.category == 10 ) response.remove(response.first); + List response = apiResponse.data!; + if (response.first.category == 2 || response.first.category == 10) response.remove(response.first); laserBodyPartsList = response; } - notifyListeners(); - + notifyListeners(); }, ); } int getDuration() { - var duration = 0; + var duration = 0; var lowerUpperLegsList = selectedBodyPartList.where((element) => element.mappingCode == "47" || element.mappingCode == "48")?.toList() ?? []; var upperLowerArmsList = selectedBodyPartList.where((element) => element.mappingCode == "40" || element.mappingCode == "41")?.toList() ?? []; @@ -1110,21 +1118,25 @@ class BookAppointmentsViewModel extends ChangeNotifier { } void setSelectedBodyPart(LaserBodyPart part) { - if(selectedBodyPartList.contains(part)){ + if (selectedBodyPartList.contains(part)) { selectedBodyPartList.remove(part); this.duration = getDuration(); notifyListeners(); } else { - if(this.duration == 90){ - dialogService.showErrorBottomSheet(message: "Duration can not exceed 90 min".needTranslation,); + if (this.duration == 90) { + dialogService.showErrorBottomSheet( + message: "Duration can not exceed 90 min".needTranslation, + ); return; } selectedBodyPartList.add(part); var duration = getDuration(); - if(duration > 90){ + if (duration > 90) { selectedBodyPartList.remove(part); - dialogService.showErrorBottomSheet(message: "Duration Exceeds 90 min".needTranslation,); + dialogService.showErrorBottomSheet( + message: "Duration Exceeds 90 min".needTranslation, + ); return; } this.duration = duration; @@ -1137,10 +1149,10 @@ class BookAppointmentsViewModel extends ChangeNotifier { } String getLaserProcedureNameWRTLanguage(LaserBodyPart part) { - if(_appState.isArabic()){ - return part.bodyPartN??""; - }else { - return part.bodyPart??""; + if (_appState.isArabic()) { + return part.bodyPartN ?? ""; + } else { + return part.bodyPart ?? ""; } } diff --git a/lib/features/contact_us/contact_us_repo.dart b/lib/features/contact_us/contact_us_repo.dart index f2b11693..3e96f919 100644 --- a/lib/features/contact_us/contact_us_repo.dart +++ b/lib/features/contact_us/contact_us_repo.dart @@ -3,14 +3,18 @@ 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/contact_us/models/req_models/request_insert_coc_item.dart'; import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_hmg_locations.dart'; import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_patient_ic_projects.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/services/logger_service.dart'; abstract class ContactUsRepo { Future>>> getHMGLocations(); Future>>> getLiveChatProjectsList(); + + Future>> insertCOCItem({required RequestInsertCOCItem requestInsertCOCItem, PatientAppointmentHistoryResponseModel? patientSelectedAppointment}); } class ContactUsRepoImp implements ContactUsRepo { @@ -72,13 +76,57 @@ class ContactUsRepoImp implements ContactUsRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { final list = response['List_PatientICProjects']; - final hmgLocations = list.map((item) => GetPatientICProjectsModel.fromJson(item as Map)).toList().cast(); + final liveChatProjectsList = list.map((item) => GetPatientICProjectsModel.fromJson(item as Map)).toList().cast(); apiResponse = GenericApiModel>( messageStatus: messageStatus, statusCode: statusCode, errorMessage: null, - data: hmgLocations, + data: liveChatProjectsList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> insertCOCItem({required RequestInsertCOCItem requestInsertCOCItem, PatientAppointmentHistoryResponseModel? patientSelectedAppointment}) async { + final Map body = requestInsertCOCItem.toJson(); + + if (patientSelectedAppointment != null) { + body['AppoinmentNo'] = patientSelectedAppointment.appointmentNo; + body['AppointmentDate'] = patientSelectedAppointment.appointmentDate; + body['ClinicID'] = patientSelectedAppointment.clinicID; + body['ClinicName'] = patientSelectedAppointment.clinicName; + body['DoctorID'] = patientSelectedAppointment.doctorID; + body['DoctorName'] = patientSelectedAppointment.doctorNameObj; + body['ProjectName'] = patientSelectedAppointment.projectName; + } + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + SEND_FEEDBACK, + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, ); } catch (e) { failure = DataParsingFailure(e.toString()); diff --git a/lib/features/contact_us/contact_us_view_model.dart b/lib/features/contact_us/contact_us_view_model.dart index 7826bd1d..11857003 100644 --- a/lib/features/contact_us/contact_us_view_model.dart +++ b/lib/features/contact_us/contact_us_view_model.dart @@ -1,7 +1,15 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/contact_us/contact_us_repo.dart'; +import 'package:hmg_patient_app_new/features/contact_us/models/feedback_type.dart'; +import 'package:hmg_patient_app_new/features/contact_us/models/req_models/request_insert_coc_item.dart'; import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_hmg_locations.dart'; +import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_patient_ic_projects.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/services/error_handler_service.dart'; class ContactUsViewModel extends ChangeNotifier { @@ -11,17 +19,43 @@ class ContactUsViewModel extends ChangeNotifier { bool isHMGLocationsListLoading = false; bool isHMGHospitalsListSelected = true; + bool isLiveChatProjectsListLoading = false; + bool isSendFeedbackTabSelected = true; List hmgHospitalsLocationsList = []; List hmgPharmacyLocationsList = []; + List liveChatProjectsList = []; + + int selectedLiveChatProjectIndex = -1; + + List feedbackAttachmentList = []; + + PatientAppointmentHistoryResponseModel? patientFeedbackSelectedAppointment; + + List feedbackTypeList = [ + FeedbackType(id: 1, nameEN: "Complaint for appointment", nameAR: 'شكوى على موعد'), + FeedbackType(id: 2, nameEN: "Complaint without appointment", nameAR: 'شكوى بدون موعد'), + FeedbackType(id: 3, nameEN: "Question", nameAR: 'سؤال'), + FeedbackType(id: 4, nameEN: "Appreciation", nameAR: 'تقدير'), + FeedbackType(id: 6, nameEN: "Suggestion", nameAR: 'إقتراح'), + FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد'), + ]; + + FeedbackType selectedFeedbackType = FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد'); + ContactUsViewModel({required this.contactUsRepo, required this.errorHandlerService, required this.appState}); initContactUsViewModel() { isHMGLocationsListLoading = true; isHMGHospitalsListSelected = true; + isLiveChatProjectsListLoading = true; hmgHospitalsLocationsList.clear(); hmgPharmacyLocationsList.clear(); + liveChatProjectsList.clear(); + feedbackAttachmentList.clear(); + selectedFeedbackType = FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد'); + setPatientFeedbackSelectedAppointment(null); getHMGLocations(); notifyListeners(); } @@ -31,6 +65,36 @@ class ContactUsViewModel extends ChangeNotifier { notifyListeners(); } + setSelectedLiveChatProjectIndex(int index) { + selectedLiveChatProjectIndex = index; + notifyListeners(); + } + + setIsSendFeedbackTabSelected(bool isSelected) { + isSendFeedbackTabSelected = isSelected; + notifyListeners(); + } + + setSelectedFeedbackType(FeedbackType feedbackType) { + selectedFeedbackType = feedbackType; + notifyListeners(); + } + + addFeedbackAttachment(String attachmentPath) { + feedbackAttachmentList.add(attachmentPath); + notifyListeners(); + } + + removeFeedbackAttachment(String attachmentPath) { + feedbackAttachmentList.remove(attachmentPath); + notifyListeners(); + } + + setPatientFeedbackSelectedAppointment(PatientAppointmentHistoryResponseModel? appointment) { + patientFeedbackSelectedAppointment = appointment; + notifyListeners(); + } + Future getHMGLocations({Function(dynamic)? onSuccess, Function(String)? onError}) async { isHMGLocationsListLoading = true; hmgHospitalsLocationsList.clear(); @@ -62,4 +126,73 @@ class ContactUsViewModel extends ChangeNotifier { }, ); } + + Future getLiveChatProjectsList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + isLiveChatProjectsListLoading = true; + liveChatProjectsList.clear(); + + notifyListeners(); + + final result = await contactUsRepo.getLiveChatProjectsList(); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + liveChatProjectsList = apiResponse.data!; + liveChatProjectsList.sort((a, b) => b.distanceInKilometers.compareTo(a.distanceInKilometers)); + isLiveChatProjectsListLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future insertCOCItem({required String subject, required String message, Function(dynamic)? onSuccess, Function(String)? onError}) async { + RequestInsertCOCItem requestInsertCOCItem = RequestInsertCOCItem(); + requestInsertCOCItem.attachment = feedbackAttachmentList.isNotEmpty ? feedbackAttachmentList.first : ""; + requestInsertCOCItem.title = subject; + requestInsertCOCItem.details = message; + requestInsertCOCItem.cOCTypeName = selectedFeedbackType.id.toString(); + requestInsertCOCItem.formTypeID = selectedFeedbackType.id.toString(); + requestInsertCOCItem.mobileNo = "966${Utils.getPhoneNumberWithoutZero(appState.getAuthenticatedUser()!.mobileNumber!)}"; + requestInsertCOCItem.isUserLoggedIn = true; + requestInsertCOCItem.projectID = 0; + requestInsertCOCItem.patientName = "${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}"; + requestInsertCOCItem.fileName = ""; + requestInsertCOCItem.appVersion = ApiConsts.appVersionID; + requestInsertCOCItem.uILanguage = appState.isArabic() ? "ar" : "en"; //TODO Change it to be dynamic + requestInsertCOCItem.browserInfo = Platform.localHostname; + requestInsertCOCItem.deviceInfo = Platform.localHostname; + requestInsertCOCItem.resolution = "400x847"; + requestInsertCOCItem.projectID = 0; + requestInsertCOCItem.tokenID = "C0c@@dm!n?T&A&A@Barcha202029582948"; + requestInsertCOCItem.identificationNo = int.parse(appState.getAuthenticatedUser()!.patientIdentificationNo!); + if (BASE_URL.contains('uat')) { + requestInsertCOCItem.forDemo = true; + } else { + requestInsertCOCItem.forDemo = false; + } + + final result = await contactUsRepo.insertCOCItem(requestInsertCOCItem: requestInsertCOCItem, patientSelectedAppointment: patientFeedbackSelectedAppointment); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/contact_us/models/feedback_type.dart b/lib/features/contact_us/models/feedback_type.dart new file mode 100644 index 00000000..ff1025af --- /dev/null +++ b/lib/features/contact_us/models/feedback_type.dart @@ -0,0 +1,11 @@ +class FeedbackType { + final int id; + final String nameEN; + final String nameAR; + + FeedbackType({ + required this.id, + required this.nameEN, + required this.nameAR, + }); +} diff --git a/lib/features/contact_us/models/req_models/request_insert_coc_item.dart b/lib/features/contact_us/models/req_models/request_insert_coc_item.dart new file mode 100644 index 00000000..e285999c --- /dev/null +++ b/lib/features/contact_us/models/req_models/request_insert_coc_item.dart @@ -0,0 +1,137 @@ +class RequestInsertCOCItem { + bool? isUserLoggedIn; + String? mobileNo; + int? identificationNo; + int? patientID; + int? patientOutSA; + int? patientTypeID; + String? tokenID; + String? patientName; + int? projectID; + String? fileName; + String? attachment; + String? uILanguage; + String? browserInfo; + String? cOCTypeName; + String? formTypeID; + String? details; + String? deviceInfo; + String? deviceType; + String? title; + String? resolution; + double? versionID; + int? channel; + int? languageID; + String? iPAdress; + String? generalid; + String? sessionID; + bool? isDentalAllowedBackend; + int? deviceTypeID; + int? patientType; + double? appVersion; + bool? forDemo; + + RequestInsertCOCItem( + {this.isUserLoggedIn, + this.mobileNo, + this.identificationNo, + this.patientID, + this.patientOutSA, + this.patientTypeID, + this.tokenID, + this.patientName, + this.projectID, + this.fileName, + this.attachment, + this.uILanguage, + this.browserInfo, + this.cOCTypeName, + this.formTypeID, + this.details, + this.deviceInfo, + this.deviceType, + this.title, + this.resolution, + this.versionID, + this.channel, + this.languageID, + this.iPAdress, + this.generalid, + this.sessionID, + this.isDentalAllowedBackend, + this.deviceTypeID, + this.patientType, + this.appVersion, + this.forDemo}); + + RequestInsertCOCItem.fromJson(Map json) { + isUserLoggedIn = json['IsUserLoggedIn']; + mobileNo = json['MobileNo']; + identificationNo = json['IdentificationNo']; + patientID = json['PatientID']; + patientOutSA = json['PatientOutSA']; + patientTypeID = json['PatientTypeID']; + tokenID = json['TokenID']; + patientName = json['PatientName']; + projectID = json['ProjectID']; + fileName = json['FileName']; + attachment = json['Attachment']; + uILanguage = json['UILanguage']; + browserInfo = json['BrowserInfo']; + cOCTypeName = json['COCTypeName']; + formTypeID = json['FormTypeID']; + details = json['Details']; + deviceInfo = json['DeviceInfo']; + deviceType = json['DeviceType']; + title = json['Title']; + resolution = json['Resolution']; + versionID = json['VersionID']; + channel = json['Channel']; + languageID = json['LanguageID']; + iPAdress = json['IPAdress']; + generalid = json['generalid']; + sessionID = json['SessionID']; + isDentalAllowedBackend = json['isDentalAllowedBackend']; + deviceTypeID = json['DeviceTypeID']; + patientType = json['PatientType']; + appVersion = json['AppVersion']; + forDemo = json['ForDemo']; + } + + Map toJson() { + final Map data = new Map(); + data['IsUserLoggedIn'] = this.isUserLoggedIn; + data['MobileNo'] = this.mobileNo; + data['IdentificationNo'] = this.identificationNo; + data['PatientID'] = this.patientID; + data['PatientOutSA'] = this.patientOutSA; + data['PatientTypeID'] = this.patientTypeID; + data['TokenID'] = this.tokenID; + data['PatientName'] = this.patientName; + data['ProjectID'] = this.projectID; + data['FileName'] = this.fileName; + data['Attachment'] = this.attachment; + data['UILanguage'] = this.uILanguage; + data['BrowserInfo'] = this.browserInfo; + data['COCTypeName'] = this.cOCTypeName; + data['FormTypeID'] = this.formTypeID; + data['Details'] = this.details; + data['DeviceInfo'] = this.deviceInfo; + data['DeviceType'] = this.deviceType; + data['Title'] = this.title; + data['Resolution'] = this.resolution; + data['VersionID'] = this.versionID; + data['Channel'] = this.channel; + data['LanguageID'] = this.languageID; + data['IPAdress'] = this.iPAdress; + data['generalid'] = this.generalid; + data['SessionID'] = this.sessionID; + data['isDentalAllowedBackend'] = this.isDentalAllowedBackend; + data['DeviceTypeID'] = this.deviceTypeID; + data['PatientType'] = this.patientType; + data['AppVersion'] = this.appVersion; + data['ForDemo'] = this.forDemo; + + return data; + } +} diff --git a/lib/features/emergency_services/emergency_services_repo.dart b/lib/features/emergency_services/emergency_services_repo.dart index b81356ea..c63f0ee6 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'; @@ -6,9 +8,11 @@ import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/request_model/PatientER_RC.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/request_model/RRTRequestModel.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/AmbulanceRequestOrdersModel.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/EROnlineCheckInPaymentDetailsResponse.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/ProjectAvgERWaitingTime.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/RRTServiceData.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/rrt_procedures_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -16,7 +20,7 @@ import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'models/resp_model/PatientERTransportationMethod.dart'; abstract class EmergencyServicesRepo { - Future>>> getRRTProcedures(); + Future>>> getRRTProcedures(int languageId); Future>>> getNearestEr({int? id, int? projectID}); @@ -26,10 +30,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}); @@ -37,13 +46,18 @@ abstract class EmergencyServicesRepo { Future>>> getTransportationMethods({int? id}); - Future>> submitAmbulanceRequest(PatientER_RC request); Future>>> getTransportationOrders({int? id}); Future>> cancelOrder(int? iD, int patientId); + Future>> submitRRTRequest(RRTRequestModel request); + + Future>> getRRTOrders({int? id}); + + Future>> cancelRRTOrder(int? iD); + Future>> getTermsAndCondition(); } class EmergencyServicesRepoImp implements EmergencyServicesRepo { @@ -68,7 +82,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, @@ -89,8 +104,8 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { } @override - Future>>> getRRTProcedures() async { - Map mapDevice = {"ProjectID": 15}; + Future>>> getRRTProcedures(int languageId) async { + Map mapDevice = {"ProjectID": 15, "languageID":1}; try { GenericApiModel>? apiResponse; @@ -104,7 +119,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, @@ -141,7 +157,10 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { final list = response['response']['transportationservices']; - final proceduresList = list.map((item) => PatientERTransportationMethod.fromJson(item as Map)).toList().cast(); + final proceduresList = list + .map((item) => PatientERTransportationMethod.fromJson(item as Map)) + .toList() + .cast(); apiResponse = GenericApiModel>( messageStatus: messageStatus, @@ -240,7 +259,7 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { Failure? failure; await apiClient.post( body: {}, - "$GET_ALL_TRANSPORTATIONS_ORDERS?patientID=$id", + "$GET_ALL_TRANSPORTATIONS_ORDERS?patientID=$id", isRCService: true, onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; @@ -248,7 +267,8 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { final list = response['response']; - final proceduresList = list.map((item) => AmbulanceRequestOrdersModel.fromJson(item as Map)).toList().cast(); + final proceduresList = + list.map((item) => AmbulanceRequestOrdersModel.fromJson(item as Map)).toList().cast(); apiResponse = GenericApiModel>( messageStatus: messageStatus, @@ -318,13 +338,11 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { await apiClient.post( CHECK_PATIENT_ER_ADVANCE_BALANCE, body: mapDevice, - onFailure: (error, statusCode, {messageStatus, failureType}) { - failure = failureType; - }, + onFailure: (error, statusCode, {messageStatus, failureType}) => failure = failureType, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { final bool patientHasERBalance = response['BalanceAmount'] > 0; - print(patientHasERBalance); + log(patientHasERBalance.toString()); apiResponse = GenericApiModel( messageStatus: messageStatus, statusCode: statusCode, @@ -344,7 +362,6 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { } } - @override Future>> checkPatientERPaymentInformation({int? projectID}) async { Map mapDevice = {"ClinicID": 10, "ProjectID": projectID ?? 0}; @@ -381,8 +398,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": { @@ -412,7 +433,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, @@ -433,7 +454,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, @@ -545,4 +567,155 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> submitRRTRequest(RRTRequestModel request) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + body: request.toJson(), + ADD_RRT_ORDER_RC, + isRCService: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: true, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> cancelRRTOrder(int? iD) async{ + try { + GenericApiModel? apiResponse; + + Map request = {"Id": iD, "ClickButton": 14}; + + Failure? failure; + await apiClient.post( + body: request, + UPDATE_RRT_ORDER_RC, + isRCService: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: true, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + + } + + @override + Future>> getRRTOrders({int? id}) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + body: {}, + GET_ALL_RRT_ORDERS_RC, + isRCService: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['response']; + + RRTServiceData serviceData = RRTServiceData(); + list.forEach((item) { + if (item["StatusId"] == 1 || item["StatusId"] == 2) { + // Pending + serviceData.pendingOrders.add(GetCMCAllOrdersResponseModel.fromJson(item)); + } + serviceData.completedOrders.add(GetCMCAllOrdersResponseModel.fromJson(item)); + }); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: serviceData, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> getTermsAndCondition() async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + body: {}, + GET_USER_TERMS, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final agreement = response['UserAgreementContent']; + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: agreement, + ); + + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + + } + + } diff --git a/lib/features/emergency_services/emergency_services_view_model.dart b/lib/features/emergency_services/emergency_services_view_model.dart index ec651933..a7130031 100644 --- a/lib/features/emergency_services/emergency_services_view_model.dart +++ b/lib/features/emergency_services/emergency_services_view_model.dart @@ -2,22 +2,31 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart' as GMSMapServices; +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/location_util.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/doctor_response_mapper.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/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_repo.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/OrderDisplay.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/request_model/RRTRequestModel.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/EROnlineCheckInPaymentDetailsResponse.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/RRTServiceData.dart'; +import 'package:hmg_patient_app_new/features/location/location_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/AmbulanceCallingPlace.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/request_model/PatientER_RC.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/AmbulanceRequestOrdersModel.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/EROnlineCheckInPaymentDetailsResponse.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/PatientERTransportationMethod.dart' show PatientERTransportationMethod; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/ProjectAvgERWaitingTime.dart'; @@ -25,23 +34,28 @@ import 'package:hmg_patient_app_new/features/emergency_services/models/resp_mode import 'package:hmg_patient_app_new/features/location/PlaceDetails.dart' show PlaceDetails; import 'package:hmg_patient_app_new/features/location/PlacePrediction.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart'; -import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.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_repo.dart'; import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/RRT/rrt_map_screen.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/RRT/rrt_request_type_select.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/RRT/terms_and_condition.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/call_ambulance_page.dart'; -import 'package:hmg_patient_app_new/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart'; -import 'package:hmg_patient_app_new/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/requesting_services_page.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/tracking_screen.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/nearest_er_page.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart' show AppRoutes; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/expandable_bottom_sheet/model/BottomSheetType.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:hmg_patient_app_new/widgets/map/map_utility_screen.dart'; import 'package:hmg_patient_app_new/widgets/order_tracking/order_tracking_state.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:huawei_map/huawei_map.dart' as HMSCameraServices; @@ -71,7 +85,6 @@ class EmergencyServicesViewModel extends ChangeNotifier { List nearestERList = []; List nearestERFilteredList = []; - List RRTProceduresList = []; List? hospitalList; List? hmgHospitalList; @@ -83,7 +96,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { int hmcCount = 0; bool pickupFromInsideTheLocation = true; List? appointments; - List? orders = []; + List? ambulanceOrders = []; //ambulance selection data section List transportationOptions = []; @@ -91,7 +104,6 @@ class EmergencyServicesViewModel extends ChangeNotifier { AmbulanceCallingPlace callingPlace = AmbulanceCallingPlace.FROM_HOSPITAL; AmbulanceDirection ambulanceDirection = AmbulanceDirection.ONE_WAY; - late RRTProceduresResponseModel selectedRRTProcedure; bool patientHasAdvanceERBalance = false; bool isERBookAppointment = false; @@ -99,13 +111,27 @@ class EmergencyServicesViewModel extends ChangeNotifier { BottomSheetType bottomSheetType = BottomSheetType.FIXED; + ///RRT request data + List RRTProceduresList = []; + RRTProceduresResponseModel? selectedRRTProcedure; + bool agreedToTermsAndCondition = false; + RRTServiceData? ordersRRT; + TextEditingController rrtNotes = TextEditingController(); + + + List allOrders = []; + List orderDisplayList = []; + bool historyLoading= false; + OrderDislpay currentlyDisplayedOrder = OrderDislpay.ALL; + + + setSelectedRRTProcedure(RRTProceduresResponseModel procedure) { selectedRRTProcedure = procedure; notifyListeners(); } - get isGMSAvailable - { + get isGMSAvailable { return appState.isGMSAvailable; } @@ -132,11 +158,25 @@ class EmergencyServicesViewModel extends ChangeNotifier { bool isMyAppointmentsLoading = false; + String? termsAndConditions; + Future getRRTProcedures({Function(dynamic)? onSuccess, Function(String)? onError}) async { + + print("the app state is ${appState.isAuthenticated}"); + if (!appState.isAuthenticated) { + dialogService.showErrorBottomSheet( + message: "You Need To Login First To Continue".needTranslation, + onOkPressed: () { + navServices.pop(); + getIt().onLoginPressed(); + }); + return; + } + RRTProceduresList.clear(); notifyListeners(); - final result = await emergencyServicesRepo.getRRTProcedures(); + final result = await emergencyServicesRepo.getRRTProcedures(appState.getLanguageID()); result.fold( (failure) async => await errorHandlerService.handleError(failure: failure), @@ -173,7 +213,8 @@ class EmergencyServicesViewModel extends ChangeNotifier { if (query.isEmpty) { nearestERFilteredList = nearestERList; } else { - nearestERFilteredList = nearestERList.where((er) => er.projectName != null && er.projectName!.toLowerCase().contains(query.toLowerCase())).toList(); + nearestERFilteredList = + nearestERList.where((er) => er.projectName != null && er.projectName!.toLowerCase().contains(query.toLowerCase())).toList(); } notifyListeners(); } @@ -232,7 +273,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() { @@ -240,7 +281,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() { @@ -256,8 +297,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { onSuccess: (position) { updateBottomSheetState(BottomSheetType.FIXED); navServices.push( - CustomPageRoute( - page: CallAmbulancePage(), direction: AxisDirection.down), + CustomPageRoute(page: CallAmbulancePage(), direction: AxisDirection.down), ); }); } else { @@ -265,9 +305,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { message: "You Need To Login First To Continue".needTranslation, onOkPressed: () { navServices.pop(); - navServices.pushAndReplace( - AppRoutes.loginScreen - ); + navServices.pushAndReplace(AppRoutes.loginScreen); }); } } @@ -299,19 +337,20 @@ class EmergencyServicesViewModel extends ChangeNotifier { void setIsGMSAvailable(bool value) { notifyListeners(); } + Future checkPatientERAdvanceBalance({Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await emergencyServicesRepo.checkPatientERAdvanceBalance(); result.fold( // (failure) async => await errorHandlerService.handleError(failure: failure), - (failure) { + (failure) { patientHasAdvanceERBalance = false; isERBookAppointment = true; if (onSuccess != null) { onSuccess(failure.message); } }, - (apiResponse) { + (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); patientHasAdvanceERBalance = false; @@ -332,12 +371,12 @@ class EmergencyServicesViewModel extends ChangeNotifier { final result = await emergencyServicesRepo.checkPatientERPaymentInformation(projectID: selectedHospital!.iD); result.fold( - (failure) { + (failure) { if (onError != null) { onError(failure.message); } }, - (apiResponse) { + (apiResponse) { if (apiResponse.messageStatus == 2) { } else if (apiResponse.messageStatus == 1) { erOnlineCheckInPaymentDetailsResponse = apiResponse.data!; @@ -350,8 +389,9 @@ 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( + Future ER_CreateAdvancePayment( + {required String paymentMethodName, required String paymentReference, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await emergencyServicesRepo.createAdvancePaymentForER( projectID: selectedHospital!.iD, authUser: appState.getAuthenticatedUser()!, paymentAmount: erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!, @@ -359,12 +399,12 @@ class EmergencyServicesViewModel extends ChangeNotifier { paymentReference: paymentReference); result.fold( - (failure) { + (failure) { if (onError != null) { onError(failure.message); } }, - (apiResponse) { + (apiResponse) { if (apiResponse.messageStatus == 2) { } else if (apiResponse.messageStatus == 1) { // erOnlineCheckInPaymentDetailsResponse = apiResponse.data!; @@ -378,12 +418,17 @@ class EmergencyServicesViewModel extends ChangeNotifier { } Future addAdvanceNumberRequest( - {required String advanceNumber, required String paymentReference, required String appointmentNo, Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await emergencyServicesRepo.addAdvanceNumberRequest(advanceNumber: advanceNumber, paymentReference: paymentReference, appointmentNo: appointmentNo); + {required String advanceNumber, + required String paymentReference, + required String appointmentNo, + Function(dynamic)? onSuccess, + Function(String)? onError}) async { + final result = await emergencyServicesRepo.addAdvanceNumberRequest( + advanceNumber: advanceNumber, paymentReference: paymentReference, appointmentNo: appointmentNo); result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), - (apiResponse) { + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { @@ -401,12 +446,12 @@ class EmergencyServicesViewModel extends ChangeNotifier { result.fold( // (failure) async => await errorHandlerService.handleError(failure: failure), - (failure) { + (failure) { if (onError != null) { onError(failure.message); } }, - (apiResponse) { + (apiResponse) { if (apiResponse.messageStatus == 2) { if (onError != null) { onError(apiResponse.errorMessage!); @@ -426,12 +471,12 @@ class EmergencyServicesViewModel extends ChangeNotifier { result.fold( // (failure) async => await errorHandlerService.handleError(failure: failure), - (failure) { + (failure) { if (onError != null) { onError(failure.message); } }, - (apiResponse) { + (apiResponse) { if (apiResponse.messageStatus == 2) { if (onError != null) { onError(apiResponse.data["InvoiceResponse"]["Message"]); @@ -457,16 +502,13 @@ class EmergencyServicesViewModel extends ChangeNotifier { onOkPressed: () { navServices.pop(); print("inside the ok button"); - getIt().onLoginPressed(); + getIt().onLoginPressed(); }); return; } - if (transportationOptions.isNotEmpty) return; - int? id = appState.getAuthenticatedUser()?.patientId; - LoaderBottomSheet.showLoader( - loadingText: "Getting Ambulance Transport Option".needTranslation); + LoaderBottomSheet.showLoader(loadingText: "Getting Ambulance Transport Option".needTranslation); notifyListeners(); var response = await emergencyServicesRepo.getTransportationMethods(id: id); @@ -485,8 +527,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { Future getTransportationMethods() async { int? id = appState.getAuthenticatedUser()?.patientId; - LoaderBottomSheet.showLoader( - loadingText: "Getting Ambulance Transport Option".needTranslation); + LoaderBottomSheet.showLoader(loadingText: "Getting Ambulance Transport Option".needTranslation); notifyListeners(); var response = await emergencyServicesRepo.getTransportationMethods(id: id); @@ -597,11 +638,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { sourceList = hmcHospitalList; break; } - displayList = sourceList - ?.where((hospital) => - hospital.name != null && - hospital.name!.toLowerCase().contains(query.toLowerCase())) - .toList(); + displayList = sourceList?.where((hospital) => hospital.name != null && hospital.name!.toLowerCase().contains(query.toLowerCase())).toList(); notifyListeners(); } @@ -620,7 +657,6 @@ class EmergencyServicesViewModel extends ChangeNotifier { notifyListeners(); } - void setSelectedHospital(HospitalsModel? hospital) { selectedHospital = hospital; notifyListeners(); @@ -668,13 +704,12 @@ class EmergencyServicesViewModel extends ChangeNotifier { } Future updateAppointment(bool value) async { - if (value) { await getAppointments(); } else { clearAppointmentData(); } - if(appointments?.isNotEmpty == true) { + if (appointments?.isNotEmpty == true) { haveAnAppointment = value; } notifyListeners(); @@ -729,18 +764,24 @@ class EmergencyServicesViewModel extends ChangeNotifier { } Future getTransportationOrders({bool shouldNavigateToTrackingScreen = false, bool showLoader = false}) async { - if(shouldNavigateToTrackingScreen == false && showLoader ) { + if (shouldNavigateToTrackingScreen == false && showLoader) { LoaderBottomSheet.showLoader(loadingText: "Fetching Orders"); } int? id = appState.getAuthenticatedUser()?.patientId; + historyLoading = true; + notifyListeners(); var response = await emergencyServicesRepo.getTransportationOrders(id: id); - if(shouldNavigateToTrackingScreen == false && showLoader ) { - LoaderBottomSheet.hideLoader();} + if (shouldNavigateToTrackingScreen == false && showLoader) { + LoaderBottomSheet.hideLoader(); + } response.fold( (failure) async { + historyLoading = false; + notifyListeners(); if (shouldNavigateToTrackingScreen) { - navServices.pushAndRemoveUntil(CustomPageRoute(page: TrackingScreen(state: OrderTrackingState.waitingForCall)), ModalRoute.withName("/EmergencyServicesPage")); + navServices.pushAndRemoveUntil( + CustomPageRoute(page: TrackingScreen(state: OrderTrackingState.waitingForCall)), ModalRoute.withName("/EmergencyServicesPage")); } }, (apiResponse) { @@ -753,8 +794,12 @@ class EmergencyServicesViewModel extends ChangeNotifier { )), ModalRoute.withName("/EmergencyServicesPage")); } - - orders = apiResponse.data; + historyLoading = false; + ambulanceOrders = apiResponse.data; + allOrders.clear(); + allOrders.addAll(ambulanceOrders??[]); + allOrders.addAll(ordersRRT?.completedOrders??[]); + changeOrderDisplayItems(OrderDislpay.ALL); notifyListeners(); }, ); @@ -828,10 +873,10 @@ class EmergencyServicesViewModel extends ChangeNotifier { Future cancelOrder(AmbulanceRequestOrdersModel? order, {bool shouldPop = false}) async { dialogService.showCommonBottomSheetWithoutH( - message: "Do you want to cancel the order".needTranslation, + message: "Do you want to cancel the request".needTranslation, onOkPressed: () async { navServices.pop(); - LoaderBottomSheet.showLoader(loadingText: "Cancelling Order".needTranslation); + LoaderBottomSheet.showLoader(loadingText: "Cancelling request".needTranslation); var response = await emergencyServicesRepo.cancelOrder(order?.iD, appState.getAuthenticatedUser()?.patientId ?? 0); LoaderBottomSheet.hideLoader(); response.fold((failure) => errorHandlerService.handleError(failure: failure), (success) { @@ -843,4 +888,215 @@ class EmergencyServicesViewModel extends ChangeNotifier { navServices.pop(); }); } + + + RRTRequestModel createRRTRequest(GeocodeResult? result, PlaceDetails? place, PlacePrediction? placePrediction){ + AuthenticatedUser? user = appState.getAuthenticatedUser(); + if (user == null) throw Exception("Authentication Required to Continue"); + + RRTRequestModel rrtRequestModel = new RRTRequestModel(); + Procedures procedures = new Procedures(); + rrtRequestModel.procedures = []; + + + procedures.serviceID = selectedRRTProcedure?.procedureID; + + rrtRequestModel.latitude = ((result?.geometry.location.lat) ?? place?.lat); + rrtRequestModel.longitude = ((result?.geometry.location.lat) ?? place?.lat); + rrtRequestModel.additionalDetails = ""; + rrtRequestModel.nationality = user.nationalityId; + rrtRequestModel.paymentAmount = selectedRRTProcedure?.patientShareWithTax; + rrtRequestModel.nearestProjectId = 0; + rrtRequestModel.patientId = user.patientId; + rrtRequestModel.patientOutSa = user.outSa; + rrtRequestModel.procedures!.add(procedures); + + return rrtRequestModel; + } + + ///method to toggle the value for the aggremnent to the terms and conditon for the rrt + void setTermsAndConditions(bool value) { + agreedToTermsAndCondition = value; + notifyListeners(); + } + + FutureOr submitRRTRequest(GeocodeResult? result, PlaceDetails? place, PlacePrediction? placePrediction) async { + RRTRequestModel request = createRRTRequest(result, place, placePrediction); + navServices.push(CustomPageRoute(page: RequestingServicesPage())); + + var response = await emergencyServicesRepo.submitRRTRequest(request); + response.fold((failure) { + navServices.pushAndRemoveUntil( + CustomPageRoute( + page: TrackingScreen( + isRRTOrder: true, + state: OrderTrackingState.failed, + )), + ModalRoute.withName("/EmergencyServicesPage")); + }, (success) { + getRRTOrders(shouldNavigateToTrackingScreen: true); + }); + } + + Future getRRTOrders({bool shouldNavigateToTrackingScreen = false, bool showLoader = false}) async { + if(shouldNavigateToTrackingScreen == false && showLoader ) { + LoaderBottomSheet.showLoader(loadingText: "Fetching Orders"); + } + historyLoading = true; + notifyListeners(); + int? id = appState.getAuthenticatedUser()?.patientId; + + var response = await emergencyServicesRepo.getRRTOrders(id: id); + if(shouldNavigateToTrackingScreen == false && showLoader ) { + LoaderBottomSheet.hideLoader();} + response.fold( + (failure) async { + historyLoading = false; + notifyListeners(); + if (shouldNavigateToTrackingScreen) { + navServices.pushAndRemoveUntil(CustomPageRoute(page: TrackingScreen(isRRTOrder: true,state: OrderTrackingState.waitingForCall)), ModalRoute.withName("/EmergencyServicesPage")); + } + }, + (apiResponse) { + if (shouldNavigateToTrackingScreen) { + navServices.pushAndRemoveUntil( + CustomPageRoute( + page: TrackingScreen( + state: OrderTrackingState.waitingForCall, + isRRTOrder: true, + rrtOrder: apiResponse.data?.pendingOrders.first, + )), + ModalRoute.withName("/EmergencyServicesPage")); + } + historyLoading = false; + ordersRRT = apiResponse.data; + allOrders.clear(); + allOrders.addAll(ambulanceOrders??[]); + allOrders.addAll(ordersRRT?.completedOrders??[]); + changeOrderDisplayItems(OrderDislpay.ALL); + notifyListeners(); + }, + ); + } + + + FutureOr cancelRRTOrder(int? orderID, {bool shouldPop = false}) async { + dialogService.showCommonBottomSheetWithoutH( + message: "Do you want to cancel the request".needTranslation, + onOkPressed: () async { + navServices.pop(); + LoaderBottomSheet.showLoader(loadingText: "Cancelling request".needTranslation); + var response = await emergencyServicesRepo.cancelRRTOrder(orderID); + LoaderBottomSheet.hideLoader(); + response.fold((failure) => errorHandlerService.handleError(failure: failure), (success) { + getRRTOrders(); + if (shouldPop) navServices.pop(); + }); + }, + onCancelPressed: () { + navServices.pop(); + }); + } + + void changeOrderDisplayItems(OrderDislpay currentlyDisplayedOrder){ + this.currentlyDisplayedOrder = currentlyDisplayedOrder; + switch(currentlyDisplayedOrder){ + case OrderDislpay.ALL: + orderDisplayList = allOrders; + break; + case OrderDislpay.RRT: + orderDisplayList = ordersRRT?.completedOrders ?? []; + break; + case OrderDislpay.AMBULANCE: + orderDisplayList = ambulanceOrders??[]; + break; + } + notifyListeners(); + } + + void openRRT(){ + print("the app state is ${appState.isAuthenticated}"); + if (appState.isAuthenticated) { + if(agreedToTermsAndCondition == false){ + dialogService.showErrorBottomSheet(message: "You Need To Agree To Terms And Conditions".needTranslation, onOkPressed: (){ + if(navServices.context == null ) return; + showCommonBottomSheetWithoutHeight( + navServices.context!, + padding: EdgeInsets.only(top: 24.h), + titleWidget: Transform.flip( + flipX: isArabic, + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrow_back, + iconColor: Color(0xff2B353E), + fit: BoxFit.contain, + ), + ).onPress(() { + navServices.pop(); + }), + // title: "Rapid Response Team (RRT)".needTranslation, + child: RrtRequestTypeSelect(), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () { + navServices.pop(); + }, + ); + }); + return; + } + placeValueInController(); + locationUtils!.getLocation( + isShowConfirmDialog: true, + onSuccess: (position) async { + updateBottomSheetState(BottomSheetType.FIXED); + bool result = await navServices.push( + CustomPageRoute( + page: MapUtilityScreen( + confirmButtonString: "Submit Request".needTranslation, + titleString: "Select Location".needTranslation, + subTitleString: "Please select the location".needTranslation, + isGmsAvailable: appState.isGMSAvailable, + ), + direction: AxisDirection.down), + ); + if(result){ + LocationViewModel locationViewModel = getIt.get(); + GeocodeResponse? response = locationViewModel.geocodeResponse; + PlaceDetails? placeDetails = locationViewModel.placeDetails; + PlacePrediction? placePrediction = locationViewModel.selectedPrediction; + submitRRTRequest(response?.results.first, placeDetails, placePrediction); + } + + }); + } else{ + dialogService.showErrorBottomSheet( + message: "You Need To Login First To Continue".needTranslation, + onOkPressed: () { + navServices.pop(); + getIt().onLoginPressed(); + }); + } + } + clearRRTData(){ + selectedRRTProcedure = null; + } + + + FutureOr getTermsAndConditions() async { + LoaderBottomSheet.showLoader(loadingText: "Fetching Terms And Conditions".needTranslation); + var response = await emergencyServicesRepo.getTermsAndCondition(); + LoaderBottomSheet.hideLoader(); + response.fold((failure)=>errorHandlerService.handleError(failure: failure),(success){ + termsAndConditions = success.data; + print("the response terms are $termsAndConditions"); + notifyListeners(); + navServices.push( + CustomPageRoute( + page: TermsAndCondition(termsAndCondition:success.data??""), direction: AxisDirection.down), + ); + }); + } + } diff --git a/lib/features/emergency_services/models/OrderDisplay.dart b/lib/features/emergency_services/models/OrderDisplay.dart new file mode 100644 index 00000000..9f1e929d --- /dev/null +++ b/lib/features/emergency_services/models/OrderDisplay.dart @@ -0,0 +1,3 @@ +enum OrderDislpay{ + ALL,RRT,AMBULANCE +} \ No newline at end of file diff --git a/lib/features/emergency_services/models/request_model/RRTRequestModel.dart b/lib/features/emergency_services/models/request_model/RRTRequestModel.dart new file mode 100644 index 00000000..dfb5b1b2 --- /dev/null +++ b/lib/features/emergency_services/models/request_model/RRTRequestModel.dart @@ -0,0 +1,75 @@ +class RRTRequestModel { + num? patientId; + int? patientOutSa; + bool? isOutPatient; + int? nearestProjectId; + num? longitude; + num? latitude; + String? additionalDetails; + String? nationality; + num? paymentAmount; + List? procedures; + + RRTRequestModel( + {this.patientId, + this.patientOutSa, + this.isOutPatient, + this.nearestProjectId, + this.longitude, + this.latitude, + this.additionalDetails, + this.nationality, + this.paymentAmount, + this.procedures}); + + RRTRequestModel.fromJson(Map json) { + patientId = json['patientId']; + patientOutSa = json['patientOutSa']; + isOutPatient = json['isOutPatient']; + nearestProjectId = json['nearestProjectId']; + longitude = json['longitude']; + latitude = json['latitude']; + additionalDetails = json['additionalDetails']; + nationality = json['nationality']; + paymentAmount = json['paymentAmount']; + if (json['procedures'] != null) { + procedures = []; + json['procedures'].forEach((v) { + procedures!.add(new Procedures.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['patientId'] = this.patientId; + data['patientOutSa'] = this.patientOutSa; + data['isOutPatient'] = this.isOutPatient; + data['nearestProjectId'] = this.nearestProjectId; + data['longitude'] = this.longitude; + data['latitude'] = this.latitude; + data['additionalDetails'] = this.additionalDetails; + data['nationality'] = this.nationality; + data['paymentAmount'] = this.paymentAmount; + if (this.procedures != null) { + data['procedures'] = this.procedures!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Procedures { + String? serviceID; + + Procedures({this.serviceID}); + + Procedures.fromJson(Map json) { + serviceID = json['ServiceID']; + } + + Map toJson() { + final Map data = new Map(); + data['ServiceID'] = this.serviceID; + return data; + } +} diff --git a/lib/features/emergency_services/models/request_model/service_price.dart b/lib/features/emergency_services/models/request_model/service_price.dart new file mode 100644 index 00000000..abb57be6 --- /dev/null +++ b/lib/features/emergency_services/models/request_model/service_price.dart @@ -0,0 +1,53 @@ +class ServicePrice { + String? currency; + dynamic maxPrice; + dynamic maxTotalPrice; + dynamic maxVAT; + dynamic minPrice; + dynamic minTotalPrice; + dynamic minVAT; + dynamic price; + dynamic totalPrice; + dynamic vat; + + ServicePrice({ + this.currency, + this.maxPrice, + this.maxTotalPrice, + this.maxVAT, + this.minPrice, + this.minTotalPrice, + this.minVAT, + this.price, + this.totalPrice, + this.vat}); + + ServicePrice.fromJson(dynamic json) { + currency = json["Currency"]; + maxPrice = json["MaxPrice"]; + maxTotalPrice = json["MaxTotalPrice"]; + maxVAT = json["MaxVAT"]; + minPrice = json["MinPrice"]; + minTotalPrice = json["MinTotalPrice"]; + minVAT = json["MinVAT"]; + price = json["Price"]; + totalPrice = json["TotalPrice"]; + vat = json["VAT"]; + } + + Map toJson() { + var map = {}; + map["Currency"] = currency; + map["MaxPrice"] = maxPrice; + map["MaxTotalPrice"] = maxTotalPrice; + map["MaxVAT"] = maxVAT; + map["MinPrice"] = minPrice; + map["MinTotalPrice"] = minTotalPrice; + map["MinVAT"] = minVAT; + map["Price"] = price; + map["TotalPrice"] = totalPrice; + map["VAT"] = vat; + return map; + } + +} \ No newline at end of file diff --git a/lib/features/emergency_services/models/resp_model/RRTServiceData.dart b/lib/features/emergency_services/models/resp_model/RRTServiceData.dart new file mode 100644 index 00000000..e4e7ce7c --- /dev/null +++ b/lib/features/emergency_services/models/resp_model/RRTServiceData.dart @@ -0,0 +1,406 @@ +class RRTServiceData { + List pendingOrders = []; + List completedOrders = []; + ServicePrice servicePrice = ServicePrice(); +} + +class GetCMCAllOrdersResponseModel { + int? iD; + int? patientId; + int? patientOutSa; + bool? isOutPatient; + int? projectId; + int? nearestProjectId; + dynamic longitude; + dynamic latitude; + dynamic appointmentNo; + dynamic dischargeId; + int? statusId; + int? serviceId; + int? channel; + Orderpayment? orderpayment; + dynamic orderselectedservice; + dynamic wforder; + dynamic orderapprovalobj; + String? created; + dynamic createdBy; + dynamic modified; + dynamic modifiedBy; + bool? isDeleted; + String? statusText; + int? paymentStatus; + dynamic clientRequestid; + dynamic paymentStatusText; + String? projectName; + String? nearestProjectName; + dynamic paymentAmount; + WFOrder? wFOrder; + String? serviceText; + bool? isSentForApproval; + int? exaCartOrderId; + bool? isTimer; + int? timeSeconds; + int? totalPendingSeconds; + int? timeMinute; + int? timeHour; + int? timeTotalSeconds; + int? timeTotalMinute; + int? timeTotalHour; + dynamic approvalStatus; + bool? isActive; + int? clickButton; + List? procedures; + dynamic pickupLocation; + dynamic dropOffLocation; + dynamic clinicName; + dynamic doctorName; + dynamic branch; + dynamic time; + dynamic notes; + + GetCMCAllOrdersResponseModel( + {this.iD, + this.patientId, + this.patientOutSa, + this.isOutPatient, + this.projectId, + this.nearestProjectId, + this.longitude, + this.latitude, + this.appointmentNo, + this.dischargeId, + this.statusId, + this.serviceId, + this.channel, + this.orderpayment, + this.orderselectedservice, + this.wforder, + this.orderapprovalobj, + this.created, + this.createdBy, + this.modified, + this.modifiedBy, + this.isDeleted, + this.statusText, + this.paymentStatus, + this.clientRequestid, + this.paymentStatusText, + this.projectName, + this.nearestProjectName, + this.paymentAmount, + this.wFOrder, + this.serviceText, + this.isSentForApproval, + this.exaCartOrderId, + this.isTimer, + this.timeSeconds, + this.totalPendingSeconds, + this.timeMinute, + this.timeHour, + this.timeTotalSeconds, + this.timeTotalMinute, + this.timeTotalHour, + this.approvalStatus, + this.isActive, + this.clickButton, + this.procedures, + this.pickupLocation, + this.dropOffLocation, + this.clinicName, + this.doctorName, + this.branch, + this.time, + this.notes}); + + GetCMCAllOrdersResponseModel.fromJson(Map json) { + iD = json['ID']; + patientId = json['PatientId']; + patientOutSa = json['PatientOutSa']; + isOutPatient = json['IsOutPatient']; + projectId = json['ProjectId']; + nearestProjectId = json['NearestProjectId']; + longitude = json['Longitude']; + latitude = json['Latitude']; + appointmentNo = json['AppointmentNo']; + dischargeId = json['DischargeId']; + statusId = json['StatusId']; + serviceId = json['ServiceId']; + channel = json['Channel']; + orderpayment = json['orderpayment'] != null + ? new Orderpayment.fromJson(json['orderpayment']) + : null; + orderselectedservice = json['orderselectedservice']; + wforder = json['wforder']; + orderapprovalobj = json['orderapprovalobj']; + created = json['Created']; + createdBy = json['CreatedBy']; + modified = json['Modified']; + modifiedBy = json['ModifiedBy']; + isDeleted = json['IsDeleted']; + statusText = json['StatusText']; + paymentStatus = json['PaymentStatus']; + clientRequestid = json['ClientRequestid']; + paymentStatusText = json['PaymentStatusText']; + projectName = json['ProjectName']; + nearestProjectName = json['NearestProjectName']; + paymentAmount = json['PaymentAmount']; + wFOrder = json['WF_order'] != null + ? new WFOrder.fromJson(json['WF_order']) + : null; + serviceText = json['ServiceText']; + isSentForApproval = json['isSentForApproval']; + exaCartOrderId = json['ExaCart_OrderId']; + isTimer = json['isTimer']; + timeSeconds = json['TimeSeconds']; + totalPendingSeconds = json['TotalPendingSeconds']; + timeMinute = json['TimeMinute']; + timeHour = json['TimeHour']; + timeTotalSeconds = json['TimeTotalSeconds']; + timeTotalMinute = json['TimeTotalMinute']; + timeTotalHour = json['TimeTotalHour']; + approvalStatus = json['ApprovalStatus']; + isActive = json['isActive']; + clickButton = json['ClickButton']; + pickupLocation = json['PickupLocation']; + dropOffLocation = json['DropOffLocation']; + clinicName = json['clinicName']; + doctorName = json['DoctorName']; + branch = json['Branch']; + time = json['Time']; + notes = json['Notes']; + } + + Map toJson() { + final Map data = new Map(); + data['ID'] = this.iD; + data['PatientId'] = this.patientId; + data['PatientOutSa'] = this.patientOutSa; + data['IsOutPatient'] = this.isOutPatient; + data['ProjectId'] = this.projectId; + data['NearestProjectId'] = this.nearestProjectId; + data['Longitude'] = this.longitude; + data['Latitude'] = this.latitude; + data['AppointmentNo'] = this.appointmentNo; + data['DischargeId'] = this.dischargeId; + data['StatusId'] = this.statusId; + data['ServiceId'] = this.serviceId; + data['Channel'] = this.channel; + if (this.orderpayment != null) { + data['orderpayment'] = this.orderpayment!.toJson(); + } + data['orderselectedservice'] = this.orderselectedservice; + + data['wforder'] = this.wforder; + data['orderapprovalobj'] = this.orderapprovalobj; + data['Created'] = this.created; + data['CreatedBy'] = this.createdBy; + data['Modified'] = this.modified; + data['ModifiedBy'] = this.modifiedBy; + data['IsDeleted'] = this.isDeleted; + data['StatusText'] = this.statusText; + data['PaymentStatus'] = this.paymentStatus; + data['ClientRequestid'] = this.clientRequestid; + data['PaymentStatusText'] = this.paymentStatusText; + data['ProjectName'] = this.projectName; + data['NearestProjectName'] = this.nearestProjectName; + data['PaymentAmount'] = this.paymentAmount; + if (this.wFOrder != null) { + data['WF_order'] = this.wFOrder!.toJson(); + } + data['ServiceText'] = this.serviceText; + data['isSentForApproval'] = this.isSentForApproval; + data['ExaCart_OrderId'] = this.exaCartOrderId; + data['isTimer'] = this.isTimer; + data['TimeSeconds'] = this.timeSeconds; + data['TotalPendingSeconds'] = this.totalPendingSeconds; + data['TimeMinute'] = this.timeMinute; + data['TimeHour'] = this.timeHour; + data['TimeTotalSeconds'] = this.timeTotalSeconds; + data['TimeTotalMinute'] = this.timeTotalMinute; + data['TimeTotalHour'] = this.timeTotalHour; + data['ApprovalStatus'] = this.approvalStatus; + data['isActive'] = this.isActive; + data['ClickButton'] = this.clickButton; + data['PickupLocation'] = this.pickupLocation; + data['DropOffLocation'] = this.dropOffLocation; + data['clinicName'] = this.clinicName; + data['DoctorName'] = this.doctorName; + data['Branch'] = this.branch; + data['Time'] = this.time; + data['Notes'] = this.notes; + return data; + } +} + +class Orderpayment { + int? iD; + int? orderId; + dynamic clientRequestId; + dynamic totalAmount; + int? paymentStatus; + dynamic order; + String? created; + dynamic createdBy; + dynamic modified; + dynamic modifiedBy; + bool? isDeleted; + + Orderpayment( + {this.iD, + this.orderId, + this.clientRequestId, + this.totalAmount, + this.paymentStatus, + this.order, + this.created, + this.createdBy, + this.modified, + this.modifiedBy, + this.isDeleted}); + + Orderpayment.fromJson(Map json) { + iD = json['ID']; + orderId = json['OrderId']; + clientRequestId = json['ClientRequestId']; + totalAmount = json['TotalAmount']; + paymentStatus = json['PaymentStatus']; + order = json['Order']; + created = json['Created']; + createdBy = json['CreatedBy']; + modified = json['Modified']; + modifiedBy = json['ModifiedBy']; + isDeleted = json['IsDeleted']; + } + + Map toJson() { + final Map data = new Map(); + data['ID'] = this.iD; + data['OrderId'] = this.orderId; + data['ClientRequestId'] = this.clientRequestId; + data['TotalAmount'] = this.totalAmount; + data['PaymentStatus'] = this.paymentStatus; + data['Order'] = this.order; + data['Created'] = this.created; + data['CreatedBy'] = this.createdBy; + data['Modified'] = this.modified; + data['ModifiedBy'] = this.modifiedBy; + data['IsDeleted'] = this.isDeleted; + return data; + } +} + +class WFOrder { + dynamic wfButtonsDTO; + int? iD; + int? orderId; + int? previousStep; + int? nextStep; + int? serviceId; + dynamic order; + String? created; + dynamic createdBy; + dynamic modified; + dynamic modifiedBy; + bool? isDeleted; + + WFOrder( + {this.wfButtonsDTO, + this.iD, + this.orderId, + this.previousStep, + this.nextStep, + this.serviceId, + this.order, + this.created, + this.createdBy, + this.modified, + this.modifiedBy, + this.isDeleted}); + + WFOrder.fromJson(Map json) { + wfButtonsDTO = json['wf_ButtonsDTO']; + iD = json['ID']; + orderId = json['OrderId']; + previousStep = json['PreviousStep']; + nextStep = json['NextStep']; + serviceId = json['ServiceId']; + order = json['Order']; + created = json['Created']; + createdBy = json['CreatedBy']; + modified = json['Modified']; + modifiedBy = json['ModifiedBy']; + isDeleted = json['IsDeleted']; + } + + Map toJson() { + final Map data = new Map(); + data['wf_ButtonsDTO'] = this.wfButtonsDTO; + data['ID'] = this.iD; + data['OrderId'] = this.orderId; + data['PreviousStep'] = this.previousStep; + data['NextStep'] = this.nextStep; + data['ServiceId'] = this.serviceId; + data['Order'] = this.order; + data['Created'] = this.created; + data['CreatedBy'] = this.createdBy; + data['Modified'] = this.modified; + data['ModifiedBy'] = this.modifiedBy; + data['IsDeleted'] = this.isDeleted; + return data; + } +} + + +class ServicePrice { + String? currency; + dynamic maxPrice; + dynamic maxTotalPrice; + dynamic maxVAT; + dynamic minPrice; + dynamic minTotalPrice; + dynamic minVAT; + dynamic price; + dynamic totalPrice; + dynamic vat; + + ServicePrice({ + this.currency, + this.maxPrice, + this.maxTotalPrice, + this.maxVAT, + this.minPrice, + this.minTotalPrice, + this.minVAT, + this.price, + this.totalPrice, + this.vat}); + + ServicePrice.fromJson(dynamic json) { + currency = json["Currency"]; + maxPrice = json["MaxPrice"]; + maxTotalPrice = json["MaxTotalPrice"]; + maxVAT = json["MaxVAT"]; + minPrice = json["MinPrice"]; + minTotalPrice = json["MinTotalPrice"]; + minVAT = json["MinVAT"]; + price = json["Price"]; + totalPrice = json["TotalPrice"]; + vat = json["VAT"]; + } + + Map toJson() { + var map = {}; + map["Currency"] = currency; + map["MaxPrice"] = maxPrice; + map["MaxTotalPrice"] = maxTotalPrice; + map["MaxVAT"] = maxVAT; + map["MinPrice"] = minPrice; + map["MinTotalPrice"] = minTotalPrice; + map["MinVAT"] = minVAT; + map["Price"] = price; + map["TotalPrice"] = totalPrice; + map["VAT"] = vat; + return map; + } + +} \ No newline at end of file diff --git a/lib/features/hmg_services/hmg_services_repo.dart b/lib/features/hmg_services/hmg_services_repo.dart new file mode 100644 index 00000000..254d3098 --- /dev/null +++ b/lib/features/hmg_services/hmg_services_repo.dart @@ -0,0 +1,522 @@ +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'; +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/hmg_services/models/req_models/cmc_create_new_order_req_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/order_update_req_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class HmgServicesRepo { + Future>>> getAllComprehensiveCheckupOrders(); + + Future>>> getAllHomeHealthCareCheckupOrders(); + + Future>> updateCmcPresOrder(OrderUpdateRequestModel requestModel); + + Future>> updateHhcPresOrder(OrderUpdateRequestModel requestModel); + + Future>>> getAllCmcServices({required int patientID}); + + Future>>> getAllHhcServices({required int patientID}); + + Future>>> getHospitalsList(); + + Future>> addCmcOrder({ + required int projectID, + required int orderServiceID, + required List services, + }); + + Future>> addHhcOrder({ + required int projectID, + required int orderServiceID, + required List services, + }); +} + +class HmgServicesRepoImp implements HmgServicesRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + HmgServicesRepoImp({required this.apiClient, required this.loggerService}); + + @override + Future>>> getAllComprehensiveCheckupOrders() async { + Map requestBody = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.allCMCOrdersRc, + isRCService: true, + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("CMC Orders API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + List cmcOrdersList = []; + // Log the full response for debugging + // Extract MessageStatus and ErrorEndUserMessage from root level + final apiErrorMessage = response['ErrorEndUserMessage'] as String?; + // Parse the response array + if (response['response'] != null && response['response'] is List) { + final ordersList = response['response'] as List; + + for (var orderJson in ordersList) { + if (orderJson is Map) { + try { + cmcOrdersList.add(GetCMCAllOrdersResponseModel.fromJson(orderJson)); + } catch (e) { + loggerService.logError("Error parsing individual order: ${e.toString()}"); + } + } + } + } + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: apiErrorMessage ?? errorMessage, + data: cmcOrdersList, + ); + } catch (e) { + loggerService.logError("Error parsing CMC 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 getAllCmcOrders: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>>> getAllHomeHealthCareCheckupOrders() async { + Map requestBody = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.allHHCOrdersRc, + isRCService: true, + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("HHC Orders API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + List cmcOrdersList = []; + // Log the full response for debugging + // Extract MessageStatus and ErrorEndUserMessage from root level + final apiErrorMessage = response['ErrorEndUserMessage'] as String?; + // Parse the response array + if (response['response'] != null && response['response'] is List) { + final ordersList = response['response'] as List; + + for (var orderJson in ordersList) { + if (orderJson is Map) { + try { + cmcOrdersList.add(GetCMCAllOrdersResponseModel.fromJson(orderJson)); + } catch (e) { + loggerService.logError("Error parsing individual order: ${e.toString()}"); + } + } + } + } + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: apiErrorMessage ?? errorMessage, + data: cmcOrdersList, + ); + } catch (e) { + loggerService.logError("Error parsing HHC 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 getAllHHCOrders: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>>> getAllCmcServices({required int patientID}) async { + Map requestBody = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.post( + '${ApiConsts.allCMCServicesRc}?patientID=$patientID', + isRCService: true, + isAllowAny: true, + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("CMC Services API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + List cmcServicesList = []; + + if (response['response'] != null && response['response'] is List) { + final servicesList = response['response'] as List; + + for (var serviceJson in servicesList) { + if (serviceJson is Map) { + cmcServicesList.add(GetCMCServicesResponseModel.fromJson(serviceJson)); + } + } + } + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: cmcServicesList, + ); + } catch (e) { + loggerService.logError("Error parsing CMC services: ${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) { + log("Unknown error in getAllCmcServices: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>>> getAllHhcServices({required int patientID}) async { + Map requestBody = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.post( + '${ApiConsts.allHHCServicesRc}?patientID=$patientID', + isRCService: true, + isAllowAny: true, + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("HHC Services API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + List hhcServicesList = []; + + if (response['response'] != null && response['response'] is List) { + final servicesList = response['response'] as List; + + for (var serviceJson in servicesList) { + if (serviceJson is Map) { + hhcServicesList.add(GetCMCServicesResponseModel.fromJson(serviceJson)); + } + } + } + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: hhcServicesList, + ); + } catch (e) { + loggerService.logError("Error parsing HHC services: ${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) { + log("Unknown error in getAllHhcServices: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> updateCmcPresOrder(OrderUpdateRequestModel requestModel) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.updateCMCOrder, + isRCService: true, + body: requestModel.toJson(), + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("Update CMC Order API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: true, + ); + + loggerService.logInfo("CMC Order updated successfully: PresOrderID=${requestModel.presOrderID}"); + } catch (e) { + loggerService.logError("Error processing update CMC order response: ${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 updateCmcPresOrder: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> updateHhcPresOrder(OrderUpdateRequestModel requestModel) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.updateHHCOrder, + isRCService: true, + body: requestModel.toJson(), + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("Update HHC Order API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: true, + ); + + loggerService.logInfo("HHC Order updated successfully: PresOrderID=${requestModel.presOrderID}"); + } catch (e) { + loggerService.logError("Error processing update HHC order response: ${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 updateHhcPresOrder: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>>> getHospitalsList() async { + Map requestBody = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.getHospitalsList, + isRCService: false, // This uses the base HIS API URL, not RC + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("Get Hospitals List API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + List hospitalsList = []; + + loggerService.logInfo("Hospitals List Raw Response: $response"); + + if (response['ListProject'] != null && response['ListProject'] is List) { + final projectsList = response['ListProject'] as List; + + for (var projectJson in projectsList) { + try { + if (projectJson is Map) { + hospitalsList.add(HospitalsModel.fromJson(projectJson)); + } + } catch (e) { + loggerService.logError('Error parsing hospital item: ${e.toString()}'); + } + } + } else { + loggerService.logInfo('Hospitals list response array is empty or missing'); + } + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: hospitalsList, + ); + + loggerService.logInfo("Hospitals fetched successfully: ${hospitalsList.length} hospitals"); + } catch (e) { + loggerService.logError("Error parsing hospitals list: ${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 getHospitalsList: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> addCmcOrder({ + required int projectID, + required int orderServiceID, + required List services, + }) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + final requestBody = { + 'ProjectID': projectID, + 'OrderServiceID': orderServiceID, + 'procedures': services.map((service) => service.toJson()).toList(), + }; + + await apiClient.post( + ApiConsts.addCMCOrder, + isRCService: true, + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("Add CMC Order API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + int requestId = 0; + if (response is Map) { + requestId = response['response']; + } + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: requestId, + ); + + loggerService.logInfo("CMC Order added successfully: ProjectID=$projectID, OrderServiceID=$orderServiceID"); + } catch (e) { + loggerService.logError("Error processing add CMC order response: ${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 addCmcOrder: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> addHhcOrder({ + required int projectID, + required int orderServiceID, + required List services, + }) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + final requestBody = { + 'ProjectID': projectID, + 'OrderServiceID': orderServiceID, + 'procedures': services.map((service) => service.toJson()).toList(), + }; + + await apiClient.post( + ApiConsts.addHHCOrder, + isRCService: true, + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("Add HHC Order API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + int requestId = 0; + if (response is Map) { + requestId = response['response']; + } + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: requestId, + ); + + loggerService.logInfo("HHC Order added successfully: ProjectID=$projectID, OrderServiceID=$orderServiceID"); + } catch (e) { + loggerService.logError("Error processing add HHC order response: ${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 addHhcOrder: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } +} diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart new file mode 100644 index 00000000..24b7fff2 --- /dev/null +++ b/lib/features/hmg_services/hmg_services_view_model.dart @@ -0,0 +1,515 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_repo.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/cmc_create_new_order_req_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/order_update_req_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class HmgServicesViewModel extends ChangeNotifier { + final HmgServicesRepo hmgServicesRepo; + final BookAppointmentsRepo bookAppointmentsRepo; + final ErrorHandlerService errorHandlerService; + + HmgServicesViewModel({required this.bookAppointmentsRepo, required this.hmgServicesRepo, required this.errorHandlerService}); + + bool isCmcOrdersLoading = false; + bool isCmcServicesLoading = false; + bool isUpdatingOrder = false; + bool isHospitalListLoading = false; + + // HHC specific loading states + bool isHhcOrdersLoading = false; + bool isHhcServicesLoading = false; + + List cmcOrdersList = []; + List cmcServicesList = []; + List hospitalsList = []; + List filteredHospitalsList = []; + HospitalsModel? selectedHospital; + + // HHC specific lists + List hhcOrdersList = []; + List hhcServicesList = []; + + // CMC order creation state + HospitalsModel? selectedHospitalForOrder; + GetCMCServicesResponseModel? selectedServiceForOrder; + + // HHC order creation state (no hospital selection needed for home healthcare) + GetCMCServicesResponseModel? selectedServiceForHhcOrder; + + // HHC multiple services selection + List selectedHhcServices = []; + + Future getCmcOrdersList() async { + cmcOrdersList.clear(); + isCmcOrdersLoading = true; + notifyListeners(); + await getAllCmcOrders(); + } + + // Helper to sort hospitals by distance (ascending). Safely converts distanceinkMS to double. + void _sortHospitalsByDistance(List list) { + double toDouble(dynamic v) { + if (v == null) return double.infinity; + if (v is num) return v.toDouble(); + if (v is String) return double.tryParse(v) ?? double.infinity; + return double.infinity; + } + + list.sort((a, b) { + final da = toDouble(a.distanceInKilometers); + final db = toDouble(b.distanceInKilometers); + return da.compareTo(db); + }); + } + + Future getAllCmcOrders({ + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + isCmcOrdersLoading = true; + notifyListeners(); + + final result = await hmgServicesRepo.getAllComprehensiveCheckupOrders(); + + result.fold( + (failure) async { + isCmcOrdersLoading = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isCmcOrdersLoading = false; + if (apiResponse.messageStatus == 1) { + cmcOrdersList = apiResponse.data ?? []; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + } + + Future getAllCmcServices({required int patientID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + isCmcServicesLoading = true; + notifyListeners(); + + final result = await hmgServicesRepo.getAllCmcServices(patientID: patientID); + + result.fold( + (failure) async { + isCmcServicesLoading = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isCmcServicesLoading = false; + if (apiResponse.messageStatus == 1) { + cmcServicesList = apiResponse.data ?? []; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + } + + Future updateCmcPresOrder({ + required OrderUpdateRequestModel requestModel, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + isUpdatingOrder = true; + notifyListeners(); + + final result = await hmgServicesRepo.updateCmcPresOrder(requestModel); + + bool success = false; + + result.fold( + (failure) async { + isUpdatingOrder = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isUpdatingOrder = false; + if (apiResponse.messageStatus == 1) { + success = true; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + + return success; + } + + Future getHospitalsList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + isHospitalListLoading = true; + notifyListeners(); + + final result = await hmgServicesRepo.getHospitalsList(); + + result.fold( + (failure) async { + isHospitalListLoading = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isHospitalListLoading = false; + if (apiResponse.messageStatus == 1) { + hospitalsList = apiResponse.data ?? []; + filteredHospitalsList = List.from(hospitalsList); + // ensure hospitals are sorted by distance before showing + _sortHospitalsByDistance(filteredHospitalsList); + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + } + + void filterHospitalsByString(String searchText, bool isArabic) { + if (searchText.isEmpty) { + filteredHospitalsList = List.from(hospitalsList); + _sortHospitalsByDistance(filteredHospitalsList); + } else { + filteredHospitalsList = hospitalsList.where((HospitalsModel hospital) { + final name = isArabic ? (hospital.nameN ?? '') : (hospital.name ?? ''); + return name.toLowerCase().contains(searchText.toLowerCase()); + }).toList(); + _sortHospitalsByDistance(filteredHospitalsList); + } + notifyListeners(); + } + + void setSelectedHospital(HospitalsModel? hospital) { + selectedHospital = hospital; + notifyListeners(); + } + + void clearHospitalSelection() { + selectedHospital = null; + filteredHospitalsList = List.from(hospitalsList); + _sortHospitalsByDistance(filteredHospitalsList); + notifyListeners(); + } + + // CMC Order management methods + void setSelectedHospitalForOrder(HospitalsModel? hospital) { + selectedHospitalForOrder = hospital; + notifyListeners(); + } + + void setSelectedServiceForOrder(GetCMCServicesResponseModel? service) { + selectedServiceForOrder = service; + notifyListeners(); + } + + void clearOrderSelection() { + selectedHospitalForOrder = null; + selectedServiceForOrder = null; + notifyListeners(); + } + + bool get isOrderReadyToConfirm => selectedHospitalForOrder != null && selectedServiceForOrder != null; + + Future addCmcOrder({ + required int projectID, + required int orderServiceID, + required List services, + Function(int)? onSuccess, + Function(String)? onError, + }) async { + isUpdatingOrder = true; + notifyListeners(); + + final result = await hmgServicesRepo.addCmcOrder( + projectID: projectID, + orderServiceID: orderServiceID, + services: services, + ); + + int requestId = 0; + + result.fold( + (failure) async { + isUpdatingOrder = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isUpdatingOrder = false; + if (apiResponse.messageStatus == 1) { + requestId = apiResponse.data ?? 0; + notifyListeners(); + if (onSuccess != null) { + onSuccess(requestId); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + + return requestId; + } + +// ******************* HOME HEALTHCARE APIs ******************** + + Future getHhcOrdersList() async { + hhcOrdersList.clear(); + isHhcOrdersLoading = true; + notifyListeners(); + await getAllHhcOrders(); + } + + Future getAllHhcOrders({ + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + isHhcOrdersLoading = true; + notifyListeners(); + + final result = await hmgServicesRepo.getAllHomeHealthCareCheckupOrders(); + + result.fold( + (failure) async { + isHhcOrdersLoading = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isHhcOrdersLoading = false; + if (apiResponse.messageStatus == 1) { + hhcOrdersList = apiResponse.data ?? []; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + } + + Future getAllHhcServices({required int patientID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + isHhcServicesLoading = true; + notifyListeners(); + + final result = await hmgServicesRepo.getAllHhcServices(patientID: patientID); + + result.fold( + (failure) async { + isHhcServicesLoading = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isHhcServicesLoading = false; + if (apiResponse.messageStatus == 1) { + hhcServicesList = apiResponse.data ?? []; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + } + + Future updateHhcPresOrder({ + required OrderUpdateRequestModel requestModel, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + isUpdatingOrder = true; + notifyListeners(); + + final result = await hmgServicesRepo.updateHhcPresOrder(requestModel); + + bool success = false; + + result.fold( + (failure) async { + isUpdatingOrder = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isUpdatingOrder = false; + if (apiResponse.messageStatus == 1) { + success = true; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + + return success; + } + + Future addHhcOrder({ + required int projectID, + required int orderServiceID, + required List services, + Function(int)? onSuccess, + Function(String)? onError, + }) async { + isUpdatingOrder = true; + notifyListeners(); + + final result = await hmgServicesRepo.addHhcOrder( + projectID: projectID, + orderServiceID: orderServiceID, + services: services, + ); + + int requestId = 0; + + result.fold( + (failure) async { + isUpdatingOrder = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isUpdatingOrder = false; + if (apiResponse.messageStatus == 1) { + requestId = apiResponse.data ?? 0; + notifyListeners(); + if (onSuccess != null) { + onSuccess(requestId); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + + return requestId; + } + + // HHC Order management methods (no hospital selection for home healthcare) + void setSelectedServiceForHhcOrder(GetCMCServicesResponseModel? service) { + selectedServiceForHhcOrder = service; + notifyListeners(); + } + + void clearHhcOrderSelection() { + selectedServiceForHhcOrder = null; + selectedHhcServices.clear(); + notifyListeners(); + } + + bool get isHhcOrderReadyToConfirm => selectedServiceForHhcOrder != null; + + // Multiple HHC services selection methods + void toggleHhcServiceSelection(GetCMCServicesResponseModel service) { + final index = selectedHhcServices.indexWhere((s) => s.iD == service.iD); + if (index != -1) { + selectedHhcServices.removeAt(index); + } else { + selectedHhcServices.add(service); + } + notifyListeners(); + } + + bool isHhcServiceSelected(GetCMCServicesResponseModel service) { + return selectedHhcServices.any((s) => s.iD == service.iD); + } + + double getHhcSelectedServicesTotal() { + double total = 0.0; + for (var service in selectedHhcServices) { + total += (service.priceTotal ?? 0); + } + return total; + } + + void clearHhcServicesSelection() { + selectedHhcServices.clear(); + notifyListeners(); + } +} diff --git a/lib/features/hmg_services/models/req_models/cmc_create_new_order_req_model.dart b/lib/features/hmg_services/models/req_models/cmc_create_new_order_req_model.dart new file mode 100644 index 00000000..a1fcc4bb --- /dev/null +++ b/lib/features/hmg_services/models/req_models/cmc_create_new_order_req_model.dart @@ -0,0 +1,131 @@ +class CMCInsertPresOrderRequestModel { + double? versionID; + int? channel; + int? languageID; + String? iPAdress; + String? generalid; + int? patientOutSA; + String? sessionID; + bool? isDentalAllowedBackend; + int? deviceTypeID; + int? patientID; + String? tokenID; + int? patientTypeID; + int? patientType; + double? latitude; + double? longitude; + int? createdBy; + int? orderServiceID; + int? projectID; + List? patientERCMCInsertServicesList; + + CMCInsertPresOrderRequestModel( + {this.versionID, + this.channel, + this.languageID, + this.iPAdress, + this.generalid, + this.patientOutSA, + this.sessionID, + this.isDentalAllowedBackend, + this.deviceTypeID, + this.patientID, + this.tokenID, + this.patientTypeID, + this.patientType, + this.latitude, + this.longitude, + this.createdBy, + this.orderServiceID, + this.projectID, + this.patientERCMCInsertServicesList}); + + CMCInsertPresOrderRequestModel.fromJson(Map json) { + versionID = json['VersionID']; + channel = json['Channel']; + languageID = json['LanguageID']; + iPAdress = json['IPAdress']; + generalid = json['generalid']; + patientOutSA = json['PatientOutSA']; + sessionID = json['SessionID']; + isDentalAllowedBackend = json['isDentalAllowedBackend']; + deviceTypeID = json['DeviceTypeID']; + patientID = json['PatientID']; + tokenID = json['TokenID']; + patientTypeID = json['PatientTypeID']; + patientType = json['PatientType']; + latitude = json['Latitude']; + longitude = json['Longitude']; + createdBy = json['CreatedBy']; + orderServiceID = json['OrderServiceID']; + projectID = json['ProjectId']; + if (json['PatientER_CMC_InsertServicesList'] != null) { + patientERCMCInsertServicesList = []; + json['PatientER_CMC_InsertServicesList'].forEach((v) { + patientERCMCInsertServicesList!.add( + new PatientERCMCInsertServicesList.fromJson(v), + ); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['VersionID'] = this.versionID; + data['Channel'] = this.channel; + data['LanguageID'] = this.languageID; + data['IPAdress'] = this.iPAdress; + data['generalid'] = this.generalid; + data['isOutPatient'] = this.patientOutSA == 0 ? false : true; + data['SessionID'] = this.sessionID; + data['isDentalAllowedBackend'] = this.isDentalAllowedBackend; + data['DeviceTypeID'] = this.deviceTypeID; + data['TokenID'] = this.tokenID; + data['PatientTypeID'] = this.patientTypeID; + data['PatientType'] = this.patientType; + data['latitude'] = this.latitude; + data['longitude'] = this.longitude; + // data['CreatedBy'] = this.createdBy; + data['OrderServiceID'] = this.orderServiceID; + data['ProjectID'] = this.projectID; + if (this.patientERCMCInsertServicesList != null) { + data['procedures'] = this.patientERCMCInsertServicesList!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class PatientERCMCInsertServicesList { + int? recordID; + String? serviceID; + String? selectedServiceName; + String? selectedServiceNameAR; + dynamic price; + dynamic vAT; + dynamic totalPrice; + + PatientERCMCInsertServicesList( + {this.recordID, this.serviceID, this.selectedServiceName, this.selectedServiceNameAR, this.price, this.vAT, this.totalPrice}); + + PatientERCMCInsertServicesList.fromJson(Map json) { + recordID = json['RecordID']; + serviceID = json['ServiceID']; + selectedServiceName = json['selectedServiceName']; + selectedServiceNameAR = json['selectedServiceNameAR']; + price = json['Price']; + vAT = json['VAT']; + totalPrice = json['TotalPrice']; + } + + Map toJson() { + final Map data = new Map(); + data['RecordID'] = this.recordID; + data['ServiceID'] = this.serviceID; + data['selectedServiceName'] = this.selectedServiceName; + data['selectedServiceNameAR'] = this.selectedServiceNameAR; + data['Price'] = this.price; + data['VAT'] = this.vAT; + data['TotalPrice'] = this.totalPrice; + return data; + } +} diff --git a/lib/features/hmg_services/models/req_models/cmc_create_service_order_req_model.dart b/lib/features/hmg_services/models/req_models/cmc_create_service_order_req_model.dart new file mode 100644 index 00000000..af2808d9 --- /dev/null +++ b/lib/features/hmg_services/models/req_models/cmc_create_service_order_req_model.dart @@ -0,0 +1,41 @@ +class CmcCreateServiceOrderReqModel { + int? recordID; + String? serviceID; + String? selectedServiceName; + String? selectedServiceNameAR; + dynamic price; + dynamic vAT; + dynamic totalPrice; + + CmcCreateServiceOrderReqModel({ + this.recordID, + this.serviceID, + this.selectedServiceName, + this.selectedServiceNameAR, + this.price, + this.vAT, + this.totalPrice, + }); + + CmcCreateServiceOrderReqModel.fromJson(Map json) { + recordID = json['RecordID']; + serviceID = json['ServiceID']; + selectedServiceName = json['selectedServiceName']; + selectedServiceNameAR = json['selectedServiceNameAR']; + price = json['Price']; + vAT = json['VAT']; + totalPrice = json['TotalPrice']; + } + + Map toJson() { + final Map data = {}; + data['RecordID'] = recordID; + data['ServiceID'] = serviceID; + data['selectedServiceName'] = selectedServiceName; + data['selectedServiceNameAR'] = selectedServiceNameAR; + data['Price'] = price; + data['VAT'] = vAT; + data['TotalPrice'] = totalPrice; + return data; + } +} diff --git a/lib/features/hmg_services/models/req_models/order_update_req_model.dart b/lib/features/hmg_services/models/req_models/order_update_req_model.dart new file mode 100644 index 00000000..0f9964f3 --- /dev/null +++ b/lib/features/hmg_services/models/req_models/order_update_req_model.dart @@ -0,0 +1,82 @@ +class OrderUpdateRequestModel { + double? versionID; + int? channel; + int? languageID; + String? iPAdress; + String? generalid; + int? patientOutSA; + String? sessionID; + bool? isDentalAllowedBackend; + int? deviceTypeID; + int? patientID; + String? tokenID; + int? patientTypeID; + int? patientType; + int? presOrderID; + int? presOrderStatus; + int? editedBy; + String? rejectionReason; + + OrderUpdateRequestModel({ + this.versionID, + this.channel, + this.languageID, + this.iPAdress, + this.generalid, + this.patientOutSA, + this.sessionID, + this.isDentalAllowedBackend, + this.deviceTypeID, + this.patientID, + this.tokenID, + this.patientTypeID, + this.patientType, + this.presOrderID, + this.presOrderStatus, + this.editedBy, + this.rejectionReason, + }); + + OrderUpdateRequestModel.fromJson(Map json) { + versionID = json['VersionID']; + channel = json['Channel']; + languageID = json['LanguageID']; + iPAdress = json['IPAdress']; + generalid = json['generalid']; + patientOutSA = json['PatientOutSA']; + sessionID = json['SessionID']; + isDentalAllowedBackend = json['isDentalAllowedBackend']; + deviceTypeID = json['DeviceTypeID']; + patientID = json['PatientID']; + tokenID = json['TokenID']; + patientTypeID = json['PatientTypeID']; + patientType = json['PatientType']; + presOrderID = json['PresOrderID']; + presOrderStatus = json['PresOrderStatus']; + editedBy = json['EditedBy']; + rejectionReason = json['RejectionReason']; + } + + Map toJson() { + final Map data = {}; + data['VersionID'] = versionID; + data['Channel'] = channel; + data['LanguageID'] = languageID; + data['IPAdress'] = iPAdress; + data['generalid'] = generalid; + data['PatientOutSA'] = patientOutSA; + data['SessionID'] = sessionID; + data['isDentalAllowedBackend'] = isDentalAllowedBackend; + data['DeviceTypeID'] = deviceTypeID; + data['PatientID'] = patientID; + data['TokenID'] = tokenID; + data['PatientTypeID'] = patientTypeID; + data['PatientType'] = patientType; + data['Id'] = presOrderID; + data['ClickButton'] = 14; + data['PresOrderStatus'] = presOrderStatus; + data['EditedBy'] = editedBy; + data['RejectionReason'] = rejectionReason; + return data; + } +} diff --git a/lib/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart b/lib/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart new file mode 100644 index 00000000..ddd91f48 --- /dev/null +++ b/lib/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart @@ -0,0 +1,344 @@ +import 'dart:developer'; + +class GetCMCAllOrdersResponseModel { + int? iD; + int? patientId; + int? patientOutSa; + bool? isOutPatient; + int? projectId; + int? nearestProjectId; + dynamic longitude; + dynamic latitude; + dynamic appointmentNo; + dynamic dischargeId; + int? statusId; + int? serviceId; + int? channel; + Orderpayment? orderpayment; + dynamic orderselectedservice; + dynamic wforder; + dynamic orderapprovalobj; + String? created; + dynamic createdBy; + dynamic modified; + dynamic modifiedBy; + bool? isDeleted; + String? statusText; + int? paymentStatus; + dynamic clientRequestid; + dynamic paymentStatusText; + String? projectName; + String? nearestProjectName; + dynamic paymentAmount; + WFOrder? wFOrder; + String? serviceText; + bool? isSentForApproval; + int? exaCartOrderId; + bool? isTimer; + int? timeSeconds; + int? totalPendingSeconds; + int? timeMinute; + int? timeHour; + int? timeTotalSeconds; + int? timeTotalMinute; + int? timeTotalHour; + dynamic approvalStatus; + bool? isActive; + int? clickButton; + List? procedures; + dynamic pickupLocation; + dynamic dropOffLocation; + dynamic clinicName; + dynamic doctorName; + dynamic branch; + dynamic time; + dynamic notes; + + GetCMCAllOrdersResponseModel( + {this.iD, + this.patientId, + this.patientOutSa, + this.isOutPatient, + this.projectId, + this.nearestProjectId, + this.longitude, + this.latitude, + this.appointmentNo, + this.dischargeId, + this.statusId, + this.serviceId, + this.channel, + this.orderpayment, + this.orderselectedservice, + this.wforder, + this.orderapprovalobj, + this.created, + this.createdBy, + this.modified, + this.modifiedBy, + this.isDeleted, + this.statusText, + this.paymentStatus, + this.clientRequestid, + this.paymentStatusText, + this.projectName, + this.nearestProjectName, + this.paymentAmount, + this.wFOrder, + this.serviceText, + this.isSentForApproval, + this.exaCartOrderId, + this.isTimer, + this.timeSeconds, + this.totalPendingSeconds, + this.timeMinute, + this.timeHour, + this.timeTotalSeconds, + this.timeTotalMinute, + this.timeTotalHour, + this.approvalStatus, + this.isActive, + this.clickButton, + this.procedures, + this.pickupLocation, + this.dropOffLocation, + this.clinicName, + this.doctorName, + this.branch, + this.time, + this.notes}); + + GetCMCAllOrdersResponseModel.fromJson(Map json) { + log("responseJson: $json"); + iD = json['ID']; + patientId = json['PatientId']; + patientOutSa = json['PatientOutSa']; + isOutPatient = json['IsOutPatient']; + projectId = json['ProjectId']; + nearestProjectId = json['NearestProjectId']; + longitude = json['Longitude']; + latitude = json['Latitude']; + appointmentNo = json['AppointmentNo']; + dischargeId = json['DischargeId']; + statusId = json['StatusId']; + serviceId = json['ServiceId']; + channel = json['Channel']; + orderpayment = json['orderpayment'] != null ? Orderpayment.fromJson(json['orderpayment']) : null; + orderselectedservice = json['orderselectedservice']; + wforder = json['wforder']; + orderapprovalobj = json['orderapprovalobj']; + created = json['Created']; + createdBy = json['CreatedBy']; + modified = json['Modified']; + modifiedBy = json['ModifiedBy']; + isDeleted = json['IsDeleted']; + statusText = json['StatusText']; + paymentStatus = json['PaymentStatus']; + clientRequestid = json['ClientRequestid']; + paymentStatusText = json['PaymentStatusText']; + projectName = json['ProjectName']; + nearestProjectName = json['NearestProjectName']; + paymentAmount = json['PaymentAmount']; + wFOrder = json['WF_order'] != null ? WFOrder.fromJson(json['WF_order']) : null; + serviceText = json['ServiceText']; + isSentForApproval = json['isSentForApproval']; + exaCartOrderId = json['ExaCart_OrderId']; + isTimer = json['isTimer']; + timeSeconds = json['TimeSeconds']; + totalPendingSeconds = json['TotalPendingSeconds']; + timeMinute = json['TimeMinute']; + timeHour = json['TimeHour']; + timeTotalSeconds = json['TimeTotalSeconds']; + timeTotalMinute = json['TimeTotalMinute']; + timeTotalHour = json['TimeTotalHour']; + approvalStatus = json['ApprovalStatus']; + isActive = json['isActive']; + clickButton = json['ClickButton']; + pickupLocation = json['PickupLocation']; + dropOffLocation = json['DropOffLocation']; + clinicName = json['clinicName']; + doctorName = json['DoctorName']; + branch = json['Branch']; + time = json['Time']; + notes = json['Notes']; + } + + Map toJson() { + final Map data = {}; + data['ID'] = iD; + data['PatientId'] = patientId; + data['PatientOutSa'] = patientOutSa; + data['IsOutPatient'] = isOutPatient; + data['ProjectId'] = projectId; + data['NearestProjectId'] = nearestProjectId; + data['Longitude'] = longitude; + data['Latitude'] = latitude; + data['AppointmentNo'] = appointmentNo; + data['DischargeId'] = dischargeId; + data['StatusId'] = statusId; + data['ServiceId'] = serviceId; + data['Channel'] = channel; + if (orderpayment != null) { + data['orderpayment'] = orderpayment!.toJson(); + } + data['orderselectedservice'] = orderselectedservice; + + data['wforder'] = wforder; + data['orderapprovalobj'] = orderapprovalobj; + data['Created'] = created; + data['CreatedBy'] = createdBy; + data['Modified'] = modified; + data['ModifiedBy'] = modifiedBy; + data['IsDeleted'] = isDeleted; + data['StatusText'] = statusText; + data['PaymentStatus'] = paymentStatus; + data['ClientRequestid'] = clientRequestid; + data['PaymentStatusText'] = paymentStatusText; + data['ProjectName'] = projectName; + data['NearestProjectName'] = nearestProjectName; + data['PaymentAmount'] = paymentAmount; + if (wFOrder != null) { + data['WF_order'] = wFOrder!.toJson(); + } + data['ServiceText'] = serviceText; + data['isSentForApproval'] = isSentForApproval; + data['ExaCart_OrderId'] = exaCartOrderId; + data['isTimer'] = isTimer; + data['TimeSeconds'] = timeSeconds; + data['TotalPendingSeconds'] = totalPendingSeconds; + data['TimeMinute'] = timeMinute; + data['TimeHour'] = timeHour; + data['TimeTotalSeconds'] = timeTotalSeconds; + data['TimeTotalMinute'] = timeTotalMinute; + data['TimeTotalHour'] = timeTotalHour; + data['ApprovalStatus'] = approvalStatus; + data['isActive'] = isActive; + data['ClickButton'] = clickButton; + data['PickupLocation'] = pickupLocation; + data['DropOffLocation'] = dropOffLocation; + data['clinicName'] = clinicName; + data['DoctorName'] = doctorName; + data['Branch'] = branch; + data['Time'] = time; + data['Notes'] = notes; + return data; + } +} + +class Orderpayment { + int? iD; + int? orderId; + dynamic clientRequestId; + dynamic totalAmount; + int? paymentStatus; + dynamic order; + String? created; + dynamic createdBy; + dynamic modified; + dynamic modifiedBy; + bool? isDeleted; + + Orderpayment( + {this.iD, + this.orderId, + this.clientRequestId, + this.totalAmount, + this.paymentStatus, + this.order, + this.created, + this.createdBy, + this.modified, + this.modifiedBy, + this.isDeleted}); + + Orderpayment.fromJson(Map json) { + iD = json['ID']; + orderId = json['OrderId']; + clientRequestId = json['ClientRequestId']; + totalAmount = json['TotalAmount']; + paymentStatus = json['PaymentStatus']; + order = json['Order']; + created = json['Created']; + createdBy = json['CreatedBy']; + modified = json['Modified']; + modifiedBy = json['ModifiedBy']; + isDeleted = json['IsDeleted']; + } + + Map toJson() { + final Map data = {}; + data['ID'] = iD; + data['OrderId'] = orderId; + data['ClientRequestId'] = clientRequestId; + data['TotalAmount'] = totalAmount; + data['PaymentStatus'] = paymentStatus; + data['Order'] = order; + data['Created'] = created; + data['CreatedBy'] = createdBy; + data['Modified'] = modified; + data['ModifiedBy'] = modifiedBy; + data['IsDeleted'] = isDeleted; + return data; + } +} + +class WFOrder { + dynamic wfButtonsDTO; + int? iD; + int? orderId; + int? previousStep; + int? nextStep; + int? serviceId; + dynamic order; + String? created; + dynamic createdBy; + dynamic modified; + dynamic modifiedBy; + bool? isDeleted; + + WFOrder( + {this.wfButtonsDTO, + this.iD, + this.orderId, + this.previousStep, + this.nextStep, + this.serviceId, + this.order, + this.created, + this.createdBy, + this.modified, + this.modifiedBy, + this.isDeleted}); + + WFOrder.fromJson(Map json) { + wfButtonsDTO = json['wf_ButtonsDTO']; + iD = json['ID']; + orderId = json['OrderId']; + previousStep = json['PreviousStep']; + nextStep = json['NextStep']; + serviceId = json['ServiceId']; + order = json['Order']; + created = json['Created']; + createdBy = json['CreatedBy']; + modified = json['Modified']; + modifiedBy = json['ModifiedBy']; + isDeleted = json['IsDeleted']; + } + + Map toJson() { + final Map data = {}; + data['wf_ButtonsDTO'] = wfButtonsDTO; + data['ID'] = iD; + data['OrderId'] = orderId; + data['PreviousStep'] = previousStep; + data['NextStep'] = nextStep; + data['ServiceId'] = serviceId; + data['Order'] = order; + data['Created'] = created; + data['CreatedBy'] = createdBy; + data['Modified'] = modified; + data['ModifiedBy'] = modifiedBy; + data['IsDeleted'] = isDeleted; + return data; + } +} diff --git a/lib/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart b/lib/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart new file mode 100644 index 00000000..670a5822 --- /dev/null +++ b/lib/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart @@ -0,0 +1,57 @@ +class GetCMCServicesResponseModel { + int? iD; + String? serviceID; + int? orderServiceID; + String? text; + String? textN; + dynamic price; + dynamic priceVAT; + dynamic priceTotal; + bool? isEnabled; + int? orderId; + int? quantity; + + GetCMCServicesResponseModel({ + this.iD, + this.serviceID, + this.orderServiceID, + this.text, + this.textN, + this.price, + this.priceVAT, + this.priceTotal, + this.isEnabled, + this.orderId, + this.quantity, + }); + + GetCMCServicesResponseModel.fromJson(Map json) { + iD = json['ID']; + serviceID = json['ServiceID']; + orderServiceID = json['OrderServiceID']; + text = json['Text']; + textN = json['TextN']; + price = json['Price']; + priceVAT = json['PriceVAT']; + priceTotal = json['PriceTotal']; + isEnabled = json['IsEnabled']; + orderId = json['OrderId']; + quantity = json['Quantity']; + } + + Map toJson() { + final Map data = {}; + data['ID'] = this.iD; + data['ServiceID'] = this.serviceID; + data['OrderServiceID'] = this.orderServiceID; + data['Text'] = this.text; + data['TextN'] = this.textN; + data['Price'] = this.price; + data['PriceVAT'] = this.priceVAT; + data['PriceTotal'] = this.priceTotal; + data['IsEnabled'] = this.isEnabled; + data['OrderId'] = this.orderId; + data['Quantity'] = this.quantity; + return data; + } +} diff --git a/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart b/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart new file mode 100644 index 00000000..d5180aec --- /dev/null +++ b/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +class HmgServicesComponentModel { + int action; + String title; + String subTitle; + String icon; + bool isLogin; + bool isLocked; + Color bgColor; + Color textColor; + String route; + + HmgServicesComponentModel( + this.action, + this.title, + this.subTitle, + this.icon, + this.isLogin, { + this.isLocked = false, + this.bgColor = Colors.white, + this.textColor = Colors.black, + this.route = '', + }); +} diff --git a/lib/features/lab/lab_repo.dart b/lib/features/lab/lab_repo.dart index 3bb793b4..e661157d 100644 --- a/lib/features/lab/lab_repo.dart +++ b/lib/features/lab/lab_repo.dart @@ -21,6 +21,7 @@ abstract class LabRepo { Future>> getLabResultReportPDF({required PatientLabOrdersResponseModel labOrder}); + Future>> getLabResultsByAppointmentNo({required num appointmentNo, required num projectID, required num clinicID}); } class LabRepoImp implements LabRepo { @@ -135,6 +136,7 @@ class LabRepoImp implements LabRepo { request['SetupID'] = laborder!.setupID; request['ProjectID'] = laborder.projectID; request['ClinicID'] = laborder.clinicID; + request['InvoiceType'] = laborder.invoiceType ?? ""; try { GenericApiModel>? apiResponse; Failure? failure; @@ -184,6 +186,7 @@ class LabRepoImp implements LabRepo { request['SetupID'] = laborder!.setupID; request['ProjectID'] = laborder.projectID; request['ClinicID'] = laborder.clinicID; + request['InvoiceType'] = laborder.invoiceType ?? ""; try { GenericApiModel>? apiResponse; Failure? failure; @@ -278,4 +281,41 @@ class LabRepoImp implements LabRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> getLabResultsByAppointmentNo({required num appointmentNo, required num projectID, required num clinicID}) async { + Map request = {}; + request['AppointmentNo'] = appointmentNo; + request['ProjectID'] = projectID; + request['ClinicID'] = clinicID; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_PATIENT_LAB_ORDERS_BY_APPOINTMENT, + body: request, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response['ListLabResultsByAppNo'], + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index 12f0f272..bad4f894 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -9,11 +9,13 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils; import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_details.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.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:intl/intl.dart' show DateFormat; import 'package:logger/logger.dart'; @@ -75,14 +77,14 @@ class LabViewModel extends ChangeNotifier { required this.navigationService}); initLabProvider() { - if (isLabNeedToLoad) { + // if (isLabNeedToLoad) { patientLabOrders.clear(); filteredLabOrders.clear(); labOrderTests.clear(); isLabOrdersLoading = true; isLabResultsLoading = true; getPatientLabOrders(); - } + // } notifyListeners(); } @@ -92,7 +94,7 @@ class LabViewModel extends ChangeNotifier { } Future getPatientLabOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async { - if (!isLabNeedToLoad) return; + // if (!isLabNeedToLoad) return; isLabOrdersLoading = true; patientLabOrders.clear(); @@ -158,7 +160,7 @@ class LabViewModel extends ChangeNotifier { filterSuggestions() { final List labels = patientLabOrders - .expand((order) => order.testDetails!) + .expand((order) => order.testDetails ?? []) .map((detail) => detail.description) .whereType() .toList(); @@ -198,6 +200,69 @@ class LabViewModel extends ChangeNotifier { } } + Future getLabResultsByAppointmentNo( + {required num appointmentNo, + required num projectID, + required num clinicID, + required int doctorID, + required String clinicName, + required String doctorName, + required String projectName, + required String appointmentDate, + Function(dynamic)? onSuccess, + Function(String)? onError}) async { + bool isVidaPlus = Utils.isVidaPlusProject(projectID.toInt()); + final result = await labRepo.getLabResultsByAppointmentNo(appointmentNo: appointmentNo, projectID: projectID, clinicID: clinicID); + + result.fold( + (failure) async { + // await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.message); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + if (onError != null) { + onError(apiResponse.errorMessage!); + } + } else if (apiResponse.messageStatus == 1) { + if (apiResponse.data != null && apiResponse.data!.isNotEmpty) { + PatientLabOrdersResponseModel labOrder = PatientLabOrdersResponseModel(); + + labOrder.invoiceNoVP = isVidaPlus ? apiResponse.data[0]['InvoiceNo'].toString() : "0"; + labOrder.invoiceNo = isVidaPlus ? "0" : apiResponse.data[0]['InvoiceNo'].toString(); + labOrder.orderNo = apiResponse.data[0]['OrderNo'].toString(); + labOrder.invoiceType = apiResponse.data[0]['InvoiceType'].toString(); + labOrder.setupID = apiResponse.data[0]['SetupID'].toString(); + labOrder.projectID = projectID.toString(); + labOrder.clinicID = clinicID.toInt(); + labOrder.doctorID = doctorID; + labOrder.clinicDescription = clinicName; + labOrder.doctorName = doctorName; + labOrder.projectName = projectName; + labOrder.orderDate = appointmentDate; + + currentlySelectedPatientOrder = labOrder; + + getPatientLabResultByHospital(labOrder); + getPatientSpecialResult(labOrder); + + if (onSuccess != null) { + onSuccess(apiResponse); + } + navigationService.push( + CustomPageRoute( + page: LabResultByClinic(labOrder: labOrder), + ), + ); + } else {} + notifyListeners(); + } + }, + ); + } + Future getPatientLabResultByHospital( PatientLabOrdersResponseModel laborder) async { isLabResultByHospitalLoading = true; diff --git a/lib/features/location/location_view_model.dart b/lib/features/location/location_view_model.dart index e2e0cc55..c6ea34eb 100644 --- a/lib/features/location/location_view_model.dart +++ b/lib/features/location/location_view_model.dart @@ -3,6 +3,8 @@ import 'dart:async'; import 'package:flutter/foundation.dart' show ChangeNotifier; import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/src/types/camera.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/features/location/GeocodeResponse.dart'; import 'package:hmg_patient_app_new/features/location/PlaceDetails.dart'; import 'package:hmg_patient_app_new/features/location/location_repo.dart'; @@ -18,7 +20,9 @@ class LocationViewModel extends ChangeNotifier { final LocationRepo locationRepo; final ErrorHandlerService errorHandlerService; - LocationViewModel({required this.locationRepo, required this.errorHandlerService}); + LocationViewModel({required this.locationRepo, required this.errorHandlerService}){ + placeValueInController(); + } List predictions = []; PlacePrediction? selectedPrediction; @@ -28,6 +32,26 @@ class LocationViewModel extends ChangeNotifier { Location? mapCapturedLocation; + Completer? gmsController; + Completer? hmsController; + + HMSCameraServices.CameraPosition getHMSLocation() { + return HMSCameraServices.CameraPosition(target: HMSCameraServices.LatLng(getIt().userLat, getIt().userLong), zoom: 18); + } + + + GMSMapServices.CameraPosition getGMSLocation() { + return GMSMapServices.CameraPosition(target: GMSMapServices.LatLng(getIt().userLat, getIt().userLong), zoom: 18); + } + + void placeValueInController() async{ + if (await getIt().isGMSAvailable) { + gmsController = Completer(); + } else { + hmsController = Completer(); + } + } + FutureOr getPlacesPrediction(String input) async { predictions = []; isPredictionLoading= true; @@ -112,5 +136,37 @@ class LocationViewModel extends ChangeNotifier { await getPlaceDetails(placePrediction.placeID); } + void moveToCurrentLocation() { + moveController(Location(lat: getIt().userLat, lng: getIt().userLong)); + } + void moveController(Location location) { + print("moving to location"); + print("gmsController is null or not $gmsController"); + if (getIt().isGMSAvailable) { + gmsController?.future.then((controller) { + controller.animateCamera( + GMSMapServices.CameraUpdate.newCameraPosition( + GMSMapServices.CameraPosition( + target: GMSMapServices.LatLng(location.lat, location.lng), + zoom: 18, + ), + ), + ); + }); + } else { + print("hmsController is null or not $hmsController"); + + hmsController?.future.then((controller) { + controller.animateCamera( + HMSCameraServices.CameraUpdate.newCameraPosition( + HMSCameraServices.CameraPosition( + target: HMSCameraServices.LatLng(location.lat, location.lng), + zoom: 18, + ), + ), + ); + }); + } + } } \ No newline at end of file diff --git a/lib/features/my_appointments/appointment_via_region_viewmodel.dart b/lib/features/my_appointments/appointment_via_region_viewmodel.dart index 6c6354a0..4a0ffab6 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/my_appointments/models/resp_models/hospital_model.dart b/lib/features/my_appointments/models/resp_models/hospital_model.dart index 9a211d04..a807b99a 100644 --- a/lib/features/my_appointments/models/resp_models/hospital_model.dart +++ b/lib/features/my_appointments/models/resp_models/hospital_model.dart @@ -62,9 +62,9 @@ class HospitalsModel { mainProjectID = json['MainProjectID']; projectOutSA = json['ProjectOutSA']; usingInDoctorApp = json['UsingInDoctorApp']; - this.isHMC = json["IsHMC"]; - this.regionArabic = json['RegionNameN']; - this.regionEnglish = json['RegionName']; + isHMC = json["IsHMC"]; + regionArabic = json['RegionNameN']; + regionEnglish = json['RegionName']; } String? getRegionName(bool isArabic) { @@ -83,24 +83,22 @@ class HospitalsModel { Map toJson() { final Map data = new Map(); - data['Desciption'] = this.desciption; - data['DesciptionN'] = this.desciptionN; - data['ID'] = this.iD; - data['LegalName'] = this.legalName; - data['LegalNameN'] = this.legalNameN; - data['Name'] = this.name; - data['NameN'] = this.nameN; - data['PhoneNumber'] = this.phoneNumber; - data['SetupID'] = this.setupID; - data['DistanceInKilometers'] = this.distanceInKilometers; - data['IsActive'] = this.isActive; - data['Latitude'] = this.latitude; - data['Longitude'] = this.longitude; - data['MainProjectID'] = this.mainProjectID; - data['ProjectOutSA'] = this.projectOutSA; - data['UsingInDoctorApp'] = this.usingInDoctorApp; + data['Desciption'] = desciption; + data['DesciptionN'] = desciptionN; + data['ID'] = iD; + data['LegalName'] = legalName; + data['LegalNameN'] = legalNameN; + data['Name'] = name; + data['NameN'] = nameN; + data['PhoneNumber'] = phoneNumber; + data['SetupID'] = setupID; + data['DistanceInKilometers'] = distanceInKilometers; + data['IsActive'] = isActive; + data['Latitude'] = latitude; + data['Longitude'] = longitude; + data['MainProjectID'] = mainProjectID; + data['ProjectOutSA'] = projectOutSA; + data['UsingInDoctorApp'] = usingInDoctorApp; return data; } - - } diff --git a/lib/features/payfort/payfort_view_model.dart b/lib/features/payfort/payfort_view_model.dart index 59104738..db8209a5 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'; @@ -40,7 +42,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( @@ -60,7 +63,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( @@ -106,7 +110,7 @@ class PayfortViewModel extends ChangeNotifier { onError!(failure.message); }, (apiResponse) { - print(apiResponse.data); + log(apiResponse.data); if (onSuccess != null) { onSuccess(apiResponse); } @@ -116,15 +120,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); } @@ -138,7 +148,7 @@ class PayfortViewModel extends ChangeNotifier { String? applePayShaType, String? applePayShaRequestPhrase, }) async { - var sdkTokenResponse; + SdkTokenResponse? sdkTokenResponse; try { String? deviceId = await _payfort.getDeviceId(); @@ -172,7 +182,7 @@ class PayfortViewModel extends ChangeNotifier { }, ); } catch (e) { - print("Error here: ${e.toString()}"); + log("Error here: ${e.toString()}"); } return sdkTokenResponse; } @@ -238,7 +248,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( @@ -246,7 +257,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/prescriptions/models/resp_models/prescription_delivery_response_model.dart b/lib/features/prescriptions/models/resp_models/prescription_delivery_response_model.dart new file mode 100644 index 00000000..c221e907 --- /dev/null +++ b/lib/features/prescriptions/models/resp_models/prescription_delivery_response_model.dart @@ -0,0 +1,318 @@ +class PrescriptionDeliveryResponseModel { + int? iD; + int? patientId; + int? patientOutSa; + bool? isOutPatient; + int? projectId; + int? nearestProjectId; + dynamic longitude; + dynamic latitude; + dynamic appointmentNo; + dynamic dischargeId; + int? statusId; + int? serviceId; + int? channel; + Orderpayment? orderpayment; + dynamic orderselectedservice; + dynamic wforder; + dynamic orderapprovalobj; + String? created; + dynamic createdBy; + dynamic modified; + dynamic modifiedBy; + bool? isDeleted; + String? statusText; + int? paymentStatus; + dynamic clientRequestid; + dynamic paymentStatusText; + String? projectName; + String? nearestProjectName; + dynamic paymentAmount; + WFOrder? wFOrder; + String? serviceText; + bool? isSentForApproval; + int? exaCartOrderId; + bool? isTimer; + int? timeSeconds; + int? totalPendingSeconds; + int? timeMinute; + int? timeHour; + int? timeTotalSeconds; + int? timeTotalMinute; + int? timeTotalHour; + dynamic approvalStatus; + bool? isActive; + int? clickButton; + List? procedures; + dynamic pickupLocation; + dynamic dropOffLocation; + dynamic clinicName; + dynamic doctorName; + dynamic branch; + dynamic time; + dynamic notes; + + PrescriptionDeliveryResponseModel( + {this.iD, + this.patientId, + this.patientOutSa, + this.isOutPatient, + this.projectId, + this.nearestProjectId, + this.longitude, + this.latitude, + this.appointmentNo, + this.dischargeId, + this.statusId, + this.serviceId, + this.channel, + this.orderpayment, + this.orderselectedservice, + this.wforder, + this.orderapprovalobj, + this.created, + this.createdBy, + this.modified, + this.modifiedBy, + this.isDeleted, + this.statusText, + this.paymentStatus, + this.clientRequestid, + this.paymentStatusText, + this.projectName, + this.nearestProjectName, + this.paymentAmount, + this.wFOrder, + this.serviceText, + this.isSentForApproval, + this.exaCartOrderId, + this.isTimer, + this.timeSeconds, + this.totalPendingSeconds, + this.timeMinute, + this.timeHour, + this.timeTotalSeconds, + this.timeTotalMinute, + this.timeTotalHour, + this.approvalStatus, + this.isActive, + this.clickButton, + this.procedures, + this.pickupLocation, + this.dropOffLocation, + this.clinicName, + this.doctorName, + this.branch, + this.time, + this.notes}); + + PrescriptionDeliveryResponseModel.fromJson(Map json) { + iD = json['ID']; + patientId = json['PatientId']; + patientOutSa = json['PatientOutSa']; + isOutPatient = json['IsOutPatient']; + projectId = json['ProjectId']; + nearestProjectId = json['NearestProjectId']; + longitude = json['Longitude']; + latitude = json['Latitude']; + appointmentNo = json['AppointmentNo']; + dischargeId = json['DischargeId']; + statusId = json['StatusId']; + serviceId = json['ServiceId']; + channel = json['Channel']; + orderpayment = json['orderpayment'] != null ? new Orderpayment.fromJson(json['orderpayment']) : null; + orderselectedservice = json['orderselectedservice']; + wforder = json['wforder']; + orderapprovalobj = json['orderapprovalobj']; + created = json['Created']; + createdBy = json['CreatedBy']; + modified = json['Modified']; + modifiedBy = json['ModifiedBy']; + isDeleted = json['IsDeleted']; + statusText = json['StatusText']; + paymentStatus = json['PaymentStatus']; + clientRequestid = json['ClientRequestid']; + paymentStatusText = json['PaymentStatusText']; + projectName = json['ProjectName']; + nearestProjectName = json['NearestProjectName']; + paymentAmount = json['PaymentAmount']; + wFOrder = json['WF_order'] != null ? new WFOrder.fromJson(json['WF_order']) : null; + serviceText = json['ServiceText']; + isSentForApproval = json['isSentForApproval']; + exaCartOrderId = json['ExaCart_OrderId']; + isTimer = json['isTimer']; + timeSeconds = json['TimeSeconds']; + totalPendingSeconds = json['TotalPendingSeconds']; + timeMinute = json['TimeMinute']; + timeHour = json['TimeHour']; + timeTotalSeconds = json['TimeTotalSeconds']; + timeTotalMinute = json['TimeTotalMinute']; + timeTotalHour = json['TimeTotalHour']; + approvalStatus = json['ApprovalStatus']; + isActive = json['isActive']; + clickButton = json['ClickButton']; + pickupLocation = json['PickupLocation']; + dropOffLocation = json['DropOffLocation']; + clinicName = json['clinicName']; + doctorName = json['DoctorName']; + branch = json['Branch']; + time = json['Time']; + notes = json['Notes']; + } + + Map toJson() { + final Map data = new Map(); + data['ID'] = this.iD; + data['PatientId'] = this.patientId; + data['PatientOutSa'] = this.patientOutSa; + data['IsOutPatient'] = this.isOutPatient; + data['ProjectId'] = this.projectId; + data['NearestProjectId'] = this.nearestProjectId; + data['Longitude'] = this.longitude; + data['Latitude'] = this.latitude; + data['AppointmentNo'] = this.appointmentNo; + data['DischargeId'] = this.dischargeId; + data['StatusId'] = this.statusId; + data['ServiceId'] = this.serviceId; + data['Channel'] = this.channel; + if (this.orderpayment != null) { + data['orderpayment'] = this.orderpayment!.toJson(); + } + data['orderselectedservice'] = this.orderselectedservice; + + data['wforder'] = this.wforder; + data['orderapprovalobj'] = this.orderapprovalobj; + data['Created'] = this.created; + data['CreatedBy'] = this.createdBy; + data['Modified'] = this.modified; + data['ModifiedBy'] = this.modifiedBy; + data['IsDeleted'] = this.isDeleted; + data['StatusText'] = this.statusText; + data['PaymentStatus'] = this.paymentStatus; + data['ClientRequestid'] = this.clientRequestid; + data['PaymentStatusText'] = this.paymentStatusText; + data['ProjectName'] = this.projectName; + data['NearestProjectName'] = this.nearestProjectName; + data['PaymentAmount'] = this.paymentAmount; + if (this.wFOrder != null) { + data['WF_order'] = this.wFOrder!.toJson(); + } + data['ServiceText'] = this.serviceText; + data['isSentForApproval'] = this.isSentForApproval; + data['ExaCart_OrderId'] = this.exaCartOrderId; + data['isTimer'] = this.isTimer; + data['TimeSeconds'] = this.timeSeconds; + data['TotalPendingSeconds'] = this.totalPendingSeconds; + data['TimeMinute'] = this.timeMinute; + data['TimeHour'] = this.timeHour; + data['TimeTotalSeconds'] = this.timeTotalSeconds; + data['TimeTotalMinute'] = this.timeTotalMinute; + data['TimeTotalHour'] = this.timeTotalHour; + data['ApprovalStatus'] = this.approvalStatus; + data['isActive'] = this.isActive; + data['ClickButton'] = this.clickButton; + data['PickupLocation'] = this.pickupLocation; + data['DropOffLocation'] = this.dropOffLocation; + data['clinicName'] = this.clinicName; + data['DoctorName'] = this.doctorName; + data['Branch'] = this.branch; + data['Time'] = this.time; + data['Notes'] = this.notes; + return data; + } +} + +class Orderpayment { + int? iD; + int? orderId; + dynamic clientRequestId; + dynamic totalAmount; + int? paymentStatus; + dynamic order; + String? created; + dynamic createdBy; + dynamic modified; + dynamic modifiedBy; + bool? isDeleted; + + Orderpayment({this.iD, this.orderId, this.clientRequestId, this.totalAmount, this.paymentStatus, this.order, this.created, this.createdBy, this.modified, this.modifiedBy, this.isDeleted}); + + Orderpayment.fromJson(Map json) { + iD = json['ID']; + orderId = json['OrderId']; + clientRequestId = json['ClientRequestId']; + totalAmount = json['TotalAmount']; + paymentStatus = json['PaymentStatus']; + order = json['Order']; + created = json['Created']; + createdBy = json['CreatedBy']; + modified = json['Modified']; + modifiedBy = json['ModifiedBy']; + isDeleted = json['IsDeleted']; + } + + Map toJson() { + final Map data = new Map(); + data['ID'] = this.iD; + data['OrderId'] = this.orderId; + data['ClientRequestId'] = this.clientRequestId; + data['TotalAmount'] = this.totalAmount; + data['PaymentStatus'] = this.paymentStatus; + data['Order'] = this.order; + data['Created'] = this.created; + data['CreatedBy'] = this.createdBy; + data['Modified'] = this.modified; + data['ModifiedBy'] = this.modifiedBy; + data['IsDeleted'] = this.isDeleted; + return data; + } +} + +class WFOrder { + dynamic wfButtonsDTO; + int? iD; + int? orderId; + int? previousStep; + int? nextStep; + int? serviceId; + dynamic order; + String? created; + dynamic createdBy; + dynamic modified; + dynamic modifiedBy; + bool? isDeleted; + + WFOrder({this.wfButtonsDTO, this.iD, this.orderId, this.previousStep, this.nextStep, this.serviceId, this.order, this.created, this.createdBy, this.modified, this.modifiedBy, this.isDeleted}); + + WFOrder.fromJson(Map json) { + wfButtonsDTO = json['wf_ButtonsDTO']; + iD = json['ID']; + orderId = json['OrderId']; + previousStep = json['PreviousStep']; + nextStep = json['NextStep']; + serviceId = json['ServiceId']; + order = json['Order']; + created = json['Created']; + createdBy = json['CreatedBy']; + modified = json['Modified']; + modifiedBy = json['ModifiedBy']; + isDeleted = json['IsDeleted']; + } + + Map toJson() { + final Map data = new Map(); + data['wf_ButtonsDTO'] = this.wfButtonsDTO; + data['ID'] = this.iD; + data['OrderId'] = this.orderId; + data['PreviousStep'] = this.previousStep; + data['NextStep'] = this.nextStep; + data['ServiceId'] = this.serviceId; + data['Order'] = this.order; + data['Created'] = this.created; + data['CreatedBy'] = this.createdBy; + data['Modified'] = this.modified; + data['ModifiedBy'] = this.modifiedBy; + data['IsDeleted'] = this.isDeleted; + return data; + } +} diff --git a/lib/features/prescriptions/prescriptions_repo.dart b/lib/features/prescriptions/prescriptions_repo.dart index 2e3f1aa4..e7a4f078 100644 --- a/lib/features/prescriptions/prescriptions_repo.dart +++ b/lib/features/prescriptions/prescriptions_repo.dart @@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:dartz/dartz.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/prescriptions/models/resp_models/patient_prescriptions_response_model.dart'; +import 'package:hmg_patient_app_new/features/prescriptions/models/resp_models/prescription_delivery_response_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/models/resp_models/prescription_detail_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -15,7 +16,13 @@ abstract class PrescriptionsRepo { Future>> getPrescriptionInstructionsPDF({required PatientPrescriptionsResponseModel prescriptionsResponseModel}); - Future>> getPrescriptionPDF({required PatientPrescriptionsResponseModel prescriptionsResponseModel, required List prescriptionDetailsList}); + Future>> getPrescriptionPDF( + {required PatientPrescriptionsResponseModel prescriptionsResponseModel, required List prescriptionDetailsList}); + + Future>> submitPrescriptionDeliveryRequest( + {required String latitude, required String longitude, required String appointmentNo, required String dischargeID, required String projectID}); + + Future>> getPrescriptionOrdersList(); } class PrescriptionsRepoImp implements PrescriptionsRepo { @@ -158,7 +165,8 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { } @override - Future> getPrescriptionPDF({required PatientPrescriptionsResponseModel prescriptionsResponseModel, required List prescriptionDetailsList}) async { + Future> getPrescriptionPDF( + {required PatientPrescriptionsResponseModel prescriptionsResponseModel, required List prescriptionDetailsList}) async { Map mapDevice = { "AppointmentDate": prescriptionsResponseModel.appointmentDate, "ClinicName": prescriptionsResponseModel.clinicDescription, @@ -206,4 +214,84 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> submitPrescriptionDeliveryRequest( + {required String latitude, required String longitude, required String appointmentNo, required String dischargeID, required String projectID}) async { + Map mapDevice = { + "latitude": latitude, + "longitude": longitude, + "AppointmentNo": appointmentNo, + "DischargeID": dischargeID, + "ProjectID": projectID, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + isRCService: true, + ADD_PRESCRIPTION_ORDER_RC, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future> getPrescriptionOrdersList() async { + Map mapDevice = {}; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + isRCService: true, + GET_ALL_PRESCRIPTION_ORDERS_RC, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + + final prescriptionOrders = response['response'].map((item) => PrescriptionDeliveryResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: prescriptionOrders, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index aac25c14..693b7278 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -1,8 +1,23 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/location_util.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/location/GeocodeResponse.dart'; +import 'package:hmg_patient_app_new/features/location/PlaceDetails.dart'; +import 'package:hmg_patient_app_new/features/location/PlacePrediction.dart'; +import 'package:hmg_patient_app_new/features/location/location_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/models/resp_models/patient_prescriptions_response_model.dart'; +import 'package:hmg_patient_app_new/features/prescriptions/models/resp_models/prescription_delivery_response_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/models/resp_models/prescription_detail_response_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_repo.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_delivery_order_summary_page.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/widgets/map/map_utility_screen.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; class PrescriptionsViewModel extends ChangeNotifier { bool isPrescriptionsOrdersLoading = false; @@ -10,6 +25,7 @@ class PrescriptionsViewModel extends ChangeNotifier { PrescriptionsRepo prescriptionsRepo; ErrorHandlerService errorHandlerService; + NavigationService navServices; // Prescription Orders Lists List patientPrescriptionOrders = []; @@ -27,15 +43,22 @@ class PrescriptionsViewModel extends ChangeNotifier { String prescriptionPDFBase64Data = ""; - PrescriptionsViewModel({required this.prescriptionsRepo, required this.errorHandlerService}); + late GeocodeResponse locationGeocodeResponse; + + bool isPrescriptionsDeliveryOrdersLoading = false; + List prescriptionsOrderList = []; + + PrescriptionsViewModel({required this.prescriptionsRepo, required this.errorHandlerService, required this.navServices}); initPrescriptionsViewModel() { patientPrescriptionOrders.clear(); patientPrescriptionOrdersByClinic.clear(); patientPrescriptionOrdersByHospital.clear(); patientPrescriptionOrdersViewList.clear(); + prescriptionsOrderList.clear(); isPrescriptionsOrdersLoading = true; isSortByClinic = true; + isPrescriptionsDeliveryOrdersLoading = true; getPatientPrescriptionOrders(); notifyListeners(); } @@ -173,4 +196,88 @@ class PrescriptionsViewModel extends ChangeNotifier { }, ); } + + Future submitPrescriptionDeliveryRequest( + {required String latitude, + required String longitude, + required String appointmentNo, + required String dischargeID, + required String projectID, + Function(dynamic)? onSuccess, + Function(String)? onError}) async { + final result = await prescriptionsRepo.submitPrescriptionDeliveryRequest(latitude: latitude, longitude: longitude, appointmentNo: appointmentNo, dischargeID: dischargeID, projectID: projectID); + + result.fold( + (failure) async { + onError!(failure.message); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + void initiatePrescriptionDelivery() async { + getIt.get().getLocation( + isShowConfirmDialog: true, + onSuccess: (position) async { + bool result = await navServices.push( + CustomPageRoute( + page: MapUtilityScreen( + confirmButtonString: LocaleKeys.next.tr(), + titleString: "Select Location".needTranslation, + subTitleString: "Please select the location for prescription delivery".needTranslation, + isGmsAvailable: getIt.get().isGMSAvailable, + ), + direction: AxisDirection.down), + ); + print("Location Selected: $result"); + if (result) { + LocationViewModel locationViewModel = getIt.get(); + locationGeocodeResponse = locationViewModel.geocodeResponse!; + navServices.push( + CustomPageRoute( + page: PrescriptionDeliveryOrderSummaryPage(), + ), + ); + } + }); + } + + Future getPrescriptionOrdersList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + isPrescriptionsDeliveryOrdersLoading = true; + prescriptionsOrderList.clear(); + notifyListeners(); + + final result = await prescriptionsRepo.getPrescriptionOrdersList(); + + result.fold( + (failure) async { + isPrescriptionsDeliveryOrdersLoading = false; + notifyListeners(); + onError!(failure.message); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + isPrescriptionsDeliveryOrdersLoading = false; + notifyListeners(); + onError!(apiResponse.errorMessage!); + } else if (apiResponse.messageStatus == 1) { + isPrescriptionsDeliveryOrdersLoading = false; + prescriptionsOrderList = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/radiology/radiology_repo.dart b/lib/features/radiology/radiology_repo.dart index 0a44428f..2af3e0fa 100644 --- a/lib/features/radiology/radiology_repo.dart +++ b/lib/features/radiology/radiology_repo.dart @@ -9,11 +9,14 @@ 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}); + + Future>>> getPatientRadiologyOrderByAppointment({required num appointmentNo, required num projectID}); } class RadiologyRepoImp implements RadiologyRepo { @@ -23,7 +26,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 +43,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 +116,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 +131,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!}", @@ -159,4 +170,50 @@ class RadiologyRepoImp implements RadiologyRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getPatientRadiologyOrderByAppointment({required num appointmentNo, required num projectID}) async { + Map mapDevice = { + "AppointmentNo": appointmentNo, + "ProjectID": projectID, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_PATIENT_ORDERS, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + final radOrders; + try { + if (response['FinalRadiologyList'] != null && response['FinalRadiologyList'].length != 0) { + final list = response['FinalRadiologyList']; + 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(); + } + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: radOrders, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/radiology/radiology_view_model.dart b/lib/features/radiology/radiology_view_model.dart index 34418810..4a879063 100644 --- a/lib/features/radiology/radiology_view_model.dart +++ b/lib/features/radiology/radiology_view_model.dart @@ -1,7 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; +import 'package:hmg_patient_app_new/presentation/radiology/radiology_result_page.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'models/resp_models/patient_radiology_response_model.dart'; @@ -11,16 +15,25 @@ class RadiologyViewModel extends ChangeNotifier { RadiologyRepo radiologyRepo; ErrorHandlerService errorHandlerService; + NavigationService navigationService; List patientRadiologyOrders = []; - + List filteredRadiologyOrders = []; + List tempRadiologyOrders = []; String radiologyImageURL = ""; String patientRadiologyReportPDFBase64 = ""; - RadiologyViewModel({required this.radiologyRepo, required this.errorHandlerService}); + late List _radiologySuggestionsList = []; + + List get radiologySuggestions => _radiologySuggestionsList; + + late PatientRadiologyResponseModel patientRadiologyOrderByAppointment; - initRadiologyProvider() { + RadiologyViewModel({required this.radiologyRepo, required this.errorHandlerService, required this.navigationService}); + + initRadiologyViewModel() { patientRadiologyOrders.clear(); + filteredRadiologyOrders.clear(); isRadiologyOrdersLoading = true; isRadiologyPDFReportLoading = true; radiologyImageURL = ""; @@ -29,7 +42,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), @@ -38,7 +51,10 @@ class RadiologyViewModel extends ChangeNotifier { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { patientRadiologyOrders = apiResponse.data!; + filteredRadiologyOrders = List.from(patientRadiologyOrders); + tempRadiologyOrders = [...patientRadiologyOrders]; isRadiologyOrdersLoading = false; + filterSuggestions(); notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -48,7 +64,37 @@ class RadiologyViewModel extends ChangeNotifier { ); } - Future getRadiologyImage({required PatientRadiologyResponseModel patientRadiologyResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { + Future getPatientRadiologyOrdersByAppointment({required num appointmentNo, required num projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await radiologyRepo.getPatientRadiologyOrderByAppointment(appointmentNo: appointmentNo, projectID: projectID); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (apiResponse.data!.isNotEmpty) { + if (onSuccess != null) { + onSuccess(apiResponse); + } + navigationService.push( + CustomPageRoute( + page: RadiologyResultPage(patientRadiologyResponseModel: apiResponse.data!.first), + ), + ); + } else { + if (onError != null) { + onError("No Radiology Orders Found".needTranslation); + } + } + } + }, + ); + } + + Future getRadiologyImage( + {required PatientRadiologyResponseModel patientRadiologyResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await radiologyRepo.getRadiologyImage(patientRadiologyResponseModel: patientRadiologyResponseModel); result.fold( @@ -68,8 +114,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( @@ -93,4 +143,20 @@ class RadiologyViewModel extends ChangeNotifier { }, ); } + + filterSuggestions() { + final List labels = patientRadiologyOrders.map((detail) => detail.description).whereType().toList(); + _radiologySuggestionsList = labels.toSet().toList(); + notifyListeners(); + } + + filterRadiologyReports(String query) { + if (query.isEmpty) { + patientRadiologyOrders = tempRadiologyOrders; // reset + } else { + filteredRadiologyOrders = filteredRadiologyOrders.where((desc) => desc.description!.toLowerCase().contains(query.toLowerCase())).toList(); + patientRadiologyOrders = filteredRadiologyOrders; + } + notifyListeners(); + } } 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 00000000..c014bee7 --- /dev/null +++ b/lib/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart @@ -0,0 +1,115 @@ +// 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; + String? projectName; // Added from parent AncillaryOrderGroup + int? projectID; // Added from parent AncillaryOrderGroup + + 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, + this.projectName, + this.projectID, + }); + + factory AncillaryOrderItem.fromJson(Map json, {String? projectName, int? projectID}) => 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?, + projectName: projectName, + projectID: projectID, + ); +} 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 00000000..26abde33 --- /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 00000000..008b22cd --- /dev/null +++ b/lib/features/todo_section/todo_section_repo.dart @@ -0,0 +1,379 @@ +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; + 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, projectName: projectName, projectID: projectID)); + } + } + } + } + } + + 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 00000000..c0fb96ba --- /dev/null +++ b/lib/features/todo_section/todo_section_view_model.dart @@ -0,0 +1,242 @@ +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 514ee69f..1af80b66 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -14,6 +14,7 @@ import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.da import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/history/lab_history_viewmodel.dart'; @@ -26,6 +27,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'; @@ -131,11 +133,18 @@ void main() async { ), ChangeNotifierProvider( create: (_) => getIt.get(), - ),ChangeNotifierProvider( + ), + ChangeNotifierProvider( create: (_) => getIt.get(), ), + ChangeNotifierProvider( + create: (_) => getIt.get(), + ), ChangeNotifierProvider( create: (_) => getIt.get(), + ), + ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 79084c99..0a1eaf27 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -11,26 +11,32 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; +import 'package:hmg_patient_app_new/features/lab/lab_view_model.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/my_appointments/utils/appointment_type.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/features/radiology/radiology_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/appointment_payment_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_doctor_card.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor_request_type_select.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; +import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; +import 'package:hmg_patient_app_new/presentation/radiology/radiology_orders_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/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; -import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; import 'package:maps_launcher/maps_launcher.dart'; import 'package:provider/provider.dart'; @@ -50,14 +56,17 @@ class _AppointmentDetailsPageState extends State { late MyAppointmentsViewModel myAppointmentsViewModel; late PrescriptionsViewModel prescriptionsViewModel; late BookAppointmentsViewModel bookAppointmentsViewModel; + late ContactUsViewModel contactUsViewModel; + late LabViewModel labViewModel; + late RadiologyViewModel radiologyViewModel; @override void initState() { scheduleMicrotask(() { - if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) { - prescriptionsViewModel.setPrescriptionsDetailsLoading(); - prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); - } + // if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) { + // prescriptionsViewModel.setPrescriptionsDetailsLoading(); + // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); + // } }); super.initState(); } @@ -68,6 +77,9 @@ class _AppointmentDetailsPageState extends State { myAppointmentsViewModel = Provider.of(context, listen: false); prescriptionsViewModel = Provider.of(context, listen: false); bookAppointmentsViewModel = Provider.of(context, listen: false); + contactUsViewModel = Provider.of(context, listen: false); + labViewModel = Provider.of(context, listen: false); + radiologyViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Column( @@ -75,7 +87,16 @@ class _AppointmentDetailsPageState extends State { Expanded( child: CollapsingListView( title: "Appointment Details".needTranslation, - report: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? () {} : null, + report: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? () { + contactUsViewModel.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel); + Navigator.of(context).push( + CustomPageRoute( + page: FeedbackPage(), + ), + ); + } + : null, child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -263,219 +284,345 @@ class _AppointmentDetailsPageState extends State { SizedBox(height: 16.h), ], ) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Lab & Radiology".needTranslation.toText18(isBold: true), - SizedBox(height: 16.h), - GridView( - padding: EdgeInsets.zero, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: isTablet || isFoldable ? 3 : 2, - crossAxisSpacing: 13.w, - mainAxisSpacing: 13.w, - ), - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - children: [ - MedicalFileCard( - label: LocaleKeys.labResults.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.lab_result_icon, - iconSize: 40.w, - isLargeText: true, - ), - MedicalFileCard( - label: "Radiology Results".needTranslation, - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.radiology_icon, - iconSize: 40.w, - isLargeText: true, - ), - MedicalFileCard( - label: LocaleKeys.labResults.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.lab_result_icon, - iconSize: 40.w, - isLargeText: true, - ), - MedicalFileCard( - label: "Radiology Results".needTranslation, - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.radiology_icon, - iconSize: 40.w, - isLargeText: true, - ), - ], - ), - SizedBox(height: 16.h), - LocaleKeys.prescriptions.tr().toText18(isBold: true), - SizedBox(height: 16.h), - Consumer(builder: (context, prescriptionVM, child) { - return prescriptionVM.isPrescriptionsDetailsLoading - ? const MoviesShimmerWidget() - : Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: Colors.white, - borderRadius: 20.r, - ), - padding: EdgeInsets.all(16.w), - child: Column( - children: [ - ListView.separated( - itemCount: prescriptionVM.prescriptionDetailsList.length, - shrinkWrap: true, - padding: EdgeInsets.only(right: 8.w), - physics: NeverScrollableScrollPhysics(), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: Row( - children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.prescription_item_icon, - width: 40.h, - height: 40.h, - ), - SizedBox(width: 8.h), - Row( - mainAxisSize: MainAxisSize.max, - children: [ - Column( - children: [ - prescriptionVM.prescriptionDetailsList[index].itemDescription! - .toText12(isBold: true, maxLine: 1), - "Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}" - .needTranslation - .toText10( - weight: FontWeight.w500, - color: AppColors.greyTextColor, - letterSpacing: -0.4), - ], - ), - SizedBox(width: 68.w), - Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, - iconColor: AppColors.blackColor, - width: 18.w, - height: 13.h, - fit: BoxFit.contain, - ), - ), - ], - ), - ], - ), - ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ).onPress(() { - prescriptionVM.setPrescriptionsDetailsLoading(); - Navigator.of(context).push( - CustomPageRoute( - page: PrescriptionDetailPage(prescriptionsResponseModel: getPrescriptionRequestModel()), - ), - ); - }), - SizedBox(height: 16.h), - const Divider(color: AppColors.dividerColor), - SizedBox(height: 16.h), - Wrap( - runSpacing: 6.w, - children: [ - // Expanded( - // child: CustomButton( - // text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context), - // onPressed: () {}, - // backgroundColor: AppColors.secondaryLightRedColor, - // borderColor: AppColors.secondaryLightRedColor, - // textColor: AppColors.primaryRedColor, - // fontSize: 14, - // fontWeight: FontWeight.w500, - // borderRadius: 12.h, - // height: 40.h, - // icon: AppAssets.appointment_calendar_icon, - // iconColor: AppColors.primaryRedColor, - // iconSize: 16.h, - // ), - // ), - // SizedBox(width: 16.h), - Expanded( - child: CustomButton( - text: "Refill & Delivery".needTranslation, - onPressed: () { - Navigator.of(context) - .push( - CustomPageRoute( - page: PrescriptionsListPage(), - ), - ) - .then((val) { - prescriptionsViewModel.setPrescriptionsDetailsLoading(); - prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); - }); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - height: 40.h, - icon: AppAssets.requests, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ), - ), - - SizedBox(width: 16.w), - Expanded( - child: CustomButton( - text: "All Prescriptions".needTranslation, - onPressed: () { - Navigator.of(context) - .push( - CustomPageRoute( - page: PrescriptionsListPage(), - ), - ) - .then((val) { - prescriptionsViewModel.setPrescriptionsDetailsLoading(); - prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); - }); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - height: 40.h, - icon: AppAssets.requests, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ), - ), - ], - ), - ], + // : SizedBox.shrink() + : GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 16.h, + mainAxisSpacing: 16.w, + mainAxisExtent: 115.h, + ), + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + shrinkWrap: true, + children: [ + MedicalFileCard( + label: LocaleKeys.labResults.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.lab_result_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() async { + LoaderBottomSheet.showLoader(loadingText: "Fetching Lab Results...".needTranslation); + await labViewModel.getLabResultsByAppointmentNo( + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + doctorID: widget.patientAppointmentHistoryResponseModel.doctorID, + doctorName: widget.patientAppointmentHistoryResponseModel.doctorNameObj!, + clinicName: widget.patientAppointmentHistoryResponseModel.clinicName!, + projectName: widget.patientAppointmentHistoryResponseModel.projectName!, + appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate!, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + ); + }), + MedicalFileCard( + label: "${LocaleKeys.radiology.tr(context: context)} ${LocaleKeys.radiologySubtitle.tr(context: context)}", + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.allergy_info_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() async { + LoaderBottomSheet.showLoader(loadingText: "Fetching Radiology Results...".needTranslation); + await radiologyViewModel.getPatientRadiologyOrdersByAppointment( + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + ); + }), + MedicalFileCard( + label: LocaleKeys.prescriptions.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.prescription_item_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() async { + LoaderBottomSheet.showLoader(loadingText: "Fetching Appointment Prescriptions...".needTranslation); + await prescriptionsViewModel.getPrescriptionDetails( + getPrescriptionRequestModel(), + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + if (val.data.isNotEmpty) { + PatientPrescriptionsResponseModel patientPrescriptionsResponseModel = PatientPrescriptionsResponseModel( + doctorImageURL: widget.patientAppointmentHistoryResponseModel.doctorImageURL, + doctorName: widget.patientAppointmentHistoryResponseModel.doctorNameObj, + appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate, + clinicDescription: widget.patientAppointmentHistoryResponseModel.clinicName, + decimalDoctorRate: widget.patientAppointmentHistoryResponseModel.decimalDoctorRate, + name: widget.patientAppointmentHistoryResponseModel.projectName, + isHomeMedicineDeliverySupported: false, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + doctorID: widget.patientAppointmentHistoryResponseModel.doctorID, + setupID: widget.patientAppointmentHistoryResponseModel.setupID, + ); + Navigator.of(context).push( + CustomPageRoute( + page: PrescriptionDetailPage(isFromAppointments: true, prescriptionsResponseModel: patientPrescriptionsResponseModel), ), ); - }), - ], - ), + } else { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "You don't have any prescriptions for this appointment.".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + ); + // Navigator.of(context).push( + // CustomPageRoute( + // page: VaccineListPage(), + // ), + // ); + }), + ], + ), + // Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // "Lab & Radiology".needTranslation.toText18(isBold: true), + // SizedBox(height: 16.h), + // Row( + // children: [ + // Expanded( + // child: LabRadCard( + // icon: AppAssets.lab_result_icon, + // labelText: LocaleKeys.labResults.tr(context: context), + // // labOrderTests: ["Complete blood count", "Creatinine", "Blood Sugar"], + // // labOrderTests: labViewModel.isLabOrdersLoading ? [] : labViewModel.labOrderTests, + // labOrderTests: [], + // // isLoading: labViewModel.isLabOrdersLoading, + // isLoading: false, + // ).onPress(() { + // Navigator.of(context).push( + // CustomPageRoute( + // page: LabOrdersPage(), + // ), + // ); + // }), + // ), + // SizedBox(width: 16.h), + // Expanded( + // child: LabRadCard( + // icon: AppAssets.radiology_icon, + // labelText: LocaleKeys.radiology.tr(context: context), + // // labOrderTests: ["Chest X-ray", "Abdominal Ultrasound", "Dental X-ray"], + // labOrderTests: [], + // isLoading: false, + // ).onPress(() { + // Navigator.of(context).push( + // CustomPageRoute( + // page: RadiologyOrdersPage(), + // ), + // ); + // }), + // ), + // ], + // ), + // SizedBox(height: 16.h), + // LocaleKeys.prescriptions.tr().toText18(isBold: true), + // SizedBox(height: 16.h), + // Consumer(builder: (context, prescriptionVM, child) { + // return prescriptionVM.isPrescriptionsDetailsLoading + // ? const MoviesShimmerWidget() + // : Container( + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: Colors.white, + // borderRadius: 20.r, + // ), + // padding: EdgeInsets.all(16.w), + // child: Column( + // children: [ + // // ListView.separated( + // // itemCount: prescriptionVM.prescriptionDetailsList.length, + // // shrinkWrap: true, + // // padding: EdgeInsets.only(right: 8.w), + // // physics: NeverScrollableScrollPhysics(), + // // itemBuilder: (context, index) { + // // return AnimationConfiguration.staggeredList( + // // position: index, + // // duration: const Duration(milliseconds: 500), + // // child: SlideAnimation( + // // verticalOffset: 100.0, + // // child: FadeInAnimation( + // // child: Row( + // // children: [ + // // Utils.buildSvgWithAssets( + // // icon: AppAssets.prescription_item_icon, + // // width: 40.h, + // // height: 40.h, + // // ), + // // SizedBox(width: 8.h), + // // Row( + // // mainAxisSize: MainAxisSize.max, + // // children: [ + // // Column( + // // children: [ + // // prescriptionVM.prescriptionDetailsList[index].itemDescription! + // // .toText12(isBold: true, maxLine: 1), + // // "Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}" + // // .needTranslation + // // .toText10( + // // weight: FontWeight.w500, + // // color: AppColors.greyTextColor, + // // letterSpacing: -0.4), + // // ], + // // ), + // // SizedBox(width: 68.w), + // // Transform.flip( + // // flipX: appState.isArabic(), + // // child: Utils.buildSvgWithAssets( + // // icon: AppAssets.forward_arrow_icon, + // // iconColor: AppColors.blackColor, + // // width: 18.w, + // // height: 13.h, + // // fit: BoxFit.contain, + // // ), + // // ), + // // ], + // // ), + // // ], + // // ), + // // ), + // // ), + // // ); + // // }, + // // separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + // // ).onPress(() { + // // prescriptionVM.setPrescriptionsDetailsLoading(); + // // Navigator.of(context).push( + // // CustomPageRoute( + // // page: PrescriptionDetailPage(prescriptionsResponseModel: getPrescriptionRequestModel()), + // // ), + // // ); + // // }), + // SizedBox(height: 16.h), + // const Divider(color: AppColors.dividerColor), + // SizedBox(height: 16.h), + // // Wrap( + // // runSpacing: 6.w, + // // children: [ + // // // Expanded( + // // // child: CustomButton( + // // // text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context), + // // // onPressed: () {}, + // // // backgroundColor: AppColors.secondaryLightRedColor, + // // // borderColor: AppColors.secondaryLightRedColor, + // // // textColor: AppColors.primaryRedColor, + // // // fontSize: 14, + // // // fontWeight: FontWeight.w500, + // // // borderRadius: 12.h, + // // // height: 40.h, + // // // icon: AppAssets.appointment_calendar_icon, + // // // iconColor: AppColors.primaryRedColor, + // // // iconSize: 16.h, + // // // ), + // // // ), + // // // SizedBox(width: 16.h), + // // Expanded( + // // child: CustomButton( + // // text: "Refill & Delivery".needTranslation, + // // onPressed: () { + // // Navigator.of(context) + // // .push( + // // CustomPageRoute( + // // page: PrescriptionsListPage(), + // // ), + // // ) + // // .then((val) { + // // prescriptionsViewModel.setPrescriptionsDetailsLoading(); + // // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); + // // }); + // // }, + // // backgroundColor: AppColors.secondaryLightRedColor, + // // borderColor: AppColors.secondaryLightRedColor, + // // textColor: AppColors.primaryRedColor, + // // fontSize: 14.f, + // // fontWeight: FontWeight.w500, + // // borderRadius: 12.r, + // // height: 40.h, + // // icon: AppAssets.requests, + // // iconColor: AppColors.primaryRedColor, + // // iconSize: 16.h, + // // ), + // // ), + // // + // // SizedBox(width: 16.w), + // // Expanded( + // // child: CustomButton( + // // text: "All Prescriptions".needTranslation, + // // onPressed: () { + // // Navigator.of(context) + // // .push( + // // CustomPageRoute( + // // page: PrescriptionsListPage(), + // // ), + // // ) + // // .then((val) { + // // prescriptionsViewModel.setPrescriptionsDetailsLoading(); + // // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); + // // }); + // // }, + // // backgroundColor: AppColors.secondaryLightRedColor, + // // borderColor: AppColors.secondaryLightRedColor, + // // textColor: AppColors.primaryRedColor, + // // fontSize: 14.f, + // // fontWeight: FontWeight.w500, + // // borderRadius: 12.r, + // // height: 40.h, + // // icon: AppAssets.requests, + // // iconColor: AppColors.primaryRedColor, + // // iconSize: 16.h, + // // ), + // // ), + // // ], + // // ), + // ], + // ), + // ); + // }), + // ], + // ), ], ).paddingAll(24.w), ), @@ -548,8 +695,8 @@ class _AppointmentDetailsPageState extends State { onPressed: () { openDoctorScheduleCalendar(); }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, + backgroundColor: AppColors.successColor, + borderColor: AppColors.successColor, textColor: AppColors.whiteColor, fontSize: 16.f, fontWeight: FontWeight.w500, diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index d4e579b5..faf1c2a8 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'; @@ -110,7 +108,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), ], @@ -154,7 +153,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), @@ -182,23 +184,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, @@ -244,7 +250,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: () { @@ -274,7 +283,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), @@ -283,7 +295,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), @@ -294,7 +308,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), @@ -373,9 +390,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, @@ -418,7 +437,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, @@ -536,7 +556,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 +568,14 @@ class _AppointmentPaymentPageState extends State { } startApplePay() async { - LoaderBottomSheet.showLoader(); + showCommonBottomSheet(context, + 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 +585,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_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index e4510880..e301fbf2 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -10,6 +10,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.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'; @@ -33,7 +34,9 @@ class AppointmentCard extends StatelessWidget { final bool isFromHomePage; final bool isFromMedicalReport; final bool isForEyeMeasurements; + final bool isForFeedback; final MedicalFileViewModel? medicalFileViewModel; + final ContactUsViewModel? contactUsViewModel; final BookAppointmentsViewModel bookAppointmentsViewModel; const AppointmentCard({ @@ -45,7 +48,9 @@ class AppointmentCard extends StatelessWidget { this.isFromHomePage = false, this.isFromMedicalReport = false, this.isForEyeMeasurements = false, + this.isForFeedback = false, this.medicalFileViewModel, + this.contactUsViewModel, }); @override @@ -118,14 +123,22 @@ class AppointmentCard extends StatelessWidget { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.network( - isLoading - ? 'https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png' - : patientAppointmentHistoryResponseModel.doctorImageURL!, - width: 63.h, - height: 63.h, - fit: BoxFit.cover, - ).circle(100).toShimmer2(isShow: isLoading), + Column( + children: [ + Image.network( + isLoading ? 'https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png' : patientAppointmentHistoryResponseModel.doctorImageURL!, + width: 63.h, + height: 63.h, + fit: BoxFit.cover, + ).circle(100).toShimmer2(isShow: isLoading), + SizedBox(height: 12.h), + AppCustomChipWidget( + icon: AppAssets.rating_icon, + iconColor: AppColors.ratingColorYellow, + labelText: isLoading ? "Rating" : "Rating: ${patientAppointmentHistoryResponseModel.decimalDoctorRate}".needTranslation) + .toShimmer2(isShow: isLoading), + ], + ), SizedBox(width: 16.h), Expanded( child: Column( @@ -142,26 +155,29 @@ class AppointmentCard extends StatelessWidget { spacing: 3.h, runSpacing: 4.h, children: [ - if (!isFromHomePage) AppCustomChipWidget(labelText: isLoading ? 'Cardiology' : patientAppointmentHistoryResponseModel.clinicName!) .toShimmer2(isShow: isLoading), - if (!isFromHomePage) AppCustomChipWidget(labelText: isLoading ? 'Olaya' : patientAppointmentHistoryResponseModel.projectName!) .toShimmer2(isShow: isLoading), AppCustomChipWidget( icon: AppAssets.appointment_calendar_icon, labelText: isLoading ? 'Cardiology' - : DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false), + : "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}", ).toShimmer2(isShow: isLoading), - if (!isFromMedicalReport) - AppCustomChipWidget( - icon: AppAssets.appointment_time_icon, - labelText: isLoading - ? 'Cardiology' - : DateUtil.formatDateToTimeLang( - DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false), - ).toShimmer2(isShow: isLoading), + // if (!isFromMedicalReport) + // AppCustomChipWidget( + // icon: AppAssets.appointment_time_icon, + // labelText: isLoading + // ? 'Cardiology' + // : DateUtil.formatDateToTimeLang( + // DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false), + // ).toShimmer2(isShow: isLoading), + // AppCustomChipWidget( + // icon: AppAssets.rating_icon, + // iconColor: AppColors.ratingColorYellow, + // labelText: isLoading ? "Rating" : "Rating: ${patientAppointmentHistoryResponseModel.decimalDoctorRate}".needTranslation) + // .toShimmer2(isShow: isLoading), ], ), ], @@ -179,7 +195,11 @@ class AppointmentCard extends StatelessWidget { return CustomButton( text: 'Select appointment'.needTranslation, onPressed: () { - medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel); + if (isForFeedback) { + contactUsViewModel!.setPatientFeedbackSelectedAppointment(patientAppointmentHistoryResponseModel); + } else { + medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel); + } Navigator.pop(context, false); }, backgroundColor: AppColors.secondaryLightRedColor, @@ -313,6 +333,7 @@ class AppointmentCard extends StatelessWidget { } void _goToDetails(BuildContext context) { + if (isFromMedicalReport) return; if (isForEyeMeasurements) { Navigator.of(context).push( CustomPageRoute( diff --git a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart index 29a7b967..ad48a6de 100644 --- a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart +++ b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart @@ -1,18 +1,13 @@ -import 'package:easy_localization/easy_localization.dart' - show tr, StringTranslateExtension; +import 'package:easy_localization/easy_localization.dart' show StringTranslateExtension; import 'package:flutter/material.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/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart'; -import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart'; -import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart'; -import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:provider/provider.dart'; @@ -53,8 +48,8 @@ class HospitalBottomSheetBody extends StatelessWidget { hintText: LocaleKeys.searchHospital.tr(), controller: searchText, onChange: (value) { - appointmentsViewModel.filterHospitalListByString(value, regionalViewModel.selectedRegionId , regionalViewModel.selectedFacilityType == - FacilitySelection.HMG.name); + appointmentsViewModel.filterHospitalListByString( + value, regionalViewModel.selectedRegionId, regionalViewModel.selectedFacilityType == FacilitySelection.HMG.name); }, isEnable: true, prefix: null, @@ -77,25 +72,15 @@ class HospitalBottomSheetBody extends StatelessWidget { SizedBox( height: MediaQuery.sizeOf(context).height * .4, child: ListView.separated( - itemBuilder: (_, index) - { - var hospital = regionalViewModel.selectedFacilityType == - FacilitySelection.HMG.name - ? appointmentsViewModel - .filteredHospitalList! - .registeredDoctorMap![ - regionalViewModel.selectedRegionId!]! - .hmgDoctorList![index] - : appointmentsViewModel - .filteredHospitalList - ?.registeredDoctorMap?[ - regionalViewModel.selectedRegionId!] - ?.hmcDoctorList?[index]; + itemBuilder: (_, index) { + var hospital = regionalViewModel.selectedFacilityType == FacilitySelection.HMG.name + ? appointmentsViewModel.filteredHospitalList!.registeredDoctorMap![regionalViewModel.selectedRegionId!]!.hmgDoctorList![index] + : appointmentsViewModel.filteredHospitalList?.registeredDoctorMap?[regionalViewModel.selectedRegionId!]?.hmcDoctorList?[index]; return HospitalListItem( - hospitalData: hospital, - isLocationEnabled: appointmentsViewModel.isLocationEnabled(), - ).onPress(() { - regionalViewModel.setHospitalModel(hospital); + hospitalData: hospital, + isLocationEnabled: appointmentsViewModel.isLocationEnabled(), + ).onPress(() { + regionalViewModel.setHospitalModel(hospital); if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION) { regionalViewModel.setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION); regionalViewModel.handleLastStepForRegion(); @@ -104,21 +89,18 @@ class HospitalBottomSheetBody extends StatelessWidget { regionalViewModel.handleLastStepForClinic(); } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) { regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION); - regionalViewModel.handleLastStepForClinicForDentalAndLaser(appointmentsViewModel.selectedClinic.clinicID??-1); + regionalViewModel.handleLastStepForClinicForDentalAndLaser(appointmentsViewModel.selectedClinic.clinicID ?? -1); // regionalViewModel.handleLastStepForClinic(); } - });}, + }); + }, separatorBuilder: (_, __) => SizedBox( height: 16.h, ), - itemCount: (regionalViewModel.selectedFacilityType == - FacilitySelection.HMG.name - ? (appointmentsViewModel.filteredHospitalList?.registeredDoctorMap?[ - regionalViewModel.selectedRegionId]?.hmgDoctorList) - : (appointmentsViewModel - .filteredHospitalList - ?.registeredDoctorMap?[ - regionalViewModel.selectedRegionId]?.hmcDoctorList))?.length ?? + itemCount: (regionalViewModel.selectedFacilityType == FacilitySelection.HMG.name + ? (appointmentsViewModel.filteredHospitalList?.registeredDoctorMap?[regionalViewModel.selectedRegionId]?.hmgDoctorList) + : (appointmentsViewModel.filteredHospitalList?.registeredDoctorMap?[regionalViewModel.selectedRegionId]?.hmcDoctorList)) + ?.length ?? 0), ) ], diff --git a/lib/presentation/authentication/quick_login.dart b/lib/presentation/authentication/quick_login.dart index f10d84f7..bdeb0fff 100644 --- a/lib/presentation/authentication/quick_login.dart +++ b/lib/presentation/authentication/quick_login.dart @@ -1,11 +1,14 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.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/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -21,18 +24,13 @@ class QuickLogin extends StatefulWidget { } class QuickLoginState extends State { + final CacheService cacheService = GetIt.instance(); + @override Widget build(BuildContext context) { NavigationService navigationService = getIt.get(); - return Container( - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(24), - topRight: Radius.circular(24), - ), - ), - padding: const EdgeInsets.all(24), + return Padding( + padding: EdgeInsets.all(24.h), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, @@ -104,11 +102,12 @@ class QuickLoginState extends State { borderColor: Color(0xffED1C2B), textColor: Colors.white, icon: AppAssets.apple_finder, + height: 56.h, )), ], ), SizedBox( - height: 16, + height: 16.h, ), Row( mainAxisAlignment: MainAxisAlignment.end, @@ -117,11 +116,13 @@ class QuickLoginState extends State { child: CustomButton( text: LocaleKeys.notNow.tr(), onPressed: () { + cacheService.saveBool(key: CacheConst.quickLoginEnabled, value: false); Navigator.pop(context, "true"); }, backgroundColor: Color(0xffFEE9EA), borderColor: Color(0xffFEE9EA), textColor: Colors.red, + height: 56.h, // icon: "assets/images/svg/apple-finder.svg", )), ], diff --git a/lib/presentation/book_appointment/doctor_filter/clinic_item.dart b/lib/presentation/book_appointment/doctor_filter/clinic_item.dart index 0d5ba764..0771d1b5 100644 --- a/lib/presentation/book_appointment/doctor_filter/clinic_item.dart +++ b/lib/presentation/book_appointment/doctor_filter/clinic_item.dart @@ -37,7 +37,7 @@ class ClinicItem extends StatelessWidget { Transform.flip( flipX: isArabic, child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, + icon: AppAssets.forward_arrow_icon_small, width: 15.h, height: 15.h, fit: BoxFit.contain, diff --git a/lib/presentation/book_appointment/search_doctor_by_name.dart b/lib/presentation/book_appointment/search_doctor_by_name.dart index fabea2da..bdb67733 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/appointment_calendar.dart b/lib/presentation/book_appointment/widgets/appointment_calendar.dart index d695ea67..54ff2827 100644 --- a/lib/presentation/book_appointment/widgets/appointment_calendar.dart +++ b/lib/presentation/book_appointment/widgets/appointment_calendar.dart @@ -140,8 +140,9 @@ class _AppointmentCalendarState extends State { }, ), ), + SizedBox(height: 10.h), Transform.translate( - offset: const Offset(0.0, -20.0), + offset: const Offset(0.0, -10.0), child: selectedDateDisplay.toText16(weight: FontWeight.w500), ), //TODO: Add Next Day Span here diff --git a/lib/presentation/book_appointment/widgets/doctor_card.dart b/lib/presentation/book_appointment/widgets/doctor_card.dart index dd9d6dfb..7257dbf4 100644 --- a/lib/presentation/book_appointment/widgets/doctor_card.dart +++ b/lib/presentation/book_appointment/widgets/doctor_card.dart @@ -1,6 +1,7 @@ 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/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'; @@ -16,11 +17,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) { @@ -43,7 +50,7 @@ class DoctorCard extends StatelessWidget { : doctorsListResponseModel.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/OALAY/1439.png", width: 63.h, height: 63.h, - fit: BoxFit.fill, + fit: BoxFit.cover, ).circle(100).toShimmer2(isShow: isLoading), SizedBox(width: 8.h), Expanded( @@ -55,8 +62,24 @@ 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), + ], + ), + SizedBox(height: 2.h), + Row( + children: [ + (isLoading + ? "Consultant Cardiologist" + : doctorsListResponseModel.speciality!.isNotEmpty + ? doctorsListResponseModel.speciality!.first + : "") + .toString() + .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, maxLine: 1) + .toShimmer2(isShow: isLoading), + SizedBox(width: 6.w), Image.network( isLoading ? "https://hmgwebservices.com/Images/flag/SYR.png" : doctorsListResponseModel.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SYR.png", width: 20.h, @@ -65,21 +88,13 @@ class DoctorCard extends StatelessWidget { ).toShimmer2(isShow: isLoading), ], ), - SizedBox(height: 2.h), - (isLoading - ? "Consultant Cardiologist" - : doctorsListResponseModel.speciality!.isNotEmpty - ? doctorsListResponseModel.speciality!.first - : "") - .toString() - .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, maxLine: 1) - .toShimmer2(isShow: isLoading), ], ), ), 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), ), ], ), @@ -100,6 +115,13 @@ class DoctorCard extends StatelessWidget { iconColor: AppColors.ratingColorYellow, labelText: "Rating: ${isLoading ? 4.78 : doctorsListResponseModel.decimalDoctorRate}".needTranslation, ).toShimmer2(isShow: isLoading), + doctorsListResponseModel.nearestFreeSlot != null + ? AppCustomChipWidget( + labelText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot)).needTranslation, + backgroundColor: AppColors.successColor, + textColor: AppColors.whiteColor, + ).toShimmer2(isShow: isLoading) + : SizedBox.shrink(), ], ), SizedBox(height: 12.h), diff --git a/lib/presentation/comprehensive_checkup/cmc_order_detail_page.dart b/lib/presentation/comprehensive_checkup/cmc_order_detail_page.dart new file mode 100644 index 00000000..7547fd04 --- /dev/null +++ b/lib/presentation/comprehensive_checkup/cmc_order_detail_page.dart @@ -0,0 +1,255 @@ +import 'dart:async'; + +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/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; +import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.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:provider/provider.dart'; + +class CmcOrderDetailPage extends StatefulWidget { + const CmcOrderDetailPage({super.key}); + + @override + State createState() => _CmcOrderDetailPageState(); +} + +class _CmcOrderDetailPageState extends State { + @override + void initState() { + super.initState(); + final hmgServicesViewModel = context.read(); + scheduleMicrotask(() async { + await hmgServicesViewModel.getCmcOrdersList(); + }); + } + + Color _getStatusColor(int? statusId) { + switch (statusId) { + case 1: // Pending + return const Color(0xffCC9B14); + case 2: // Processing + return const Color(0xff2E303A); + case 3: // Completed + return const Color(0xff359846); + case 4: // Cancelled + case 6: // Rejected + case 7: // Rejected + return const Color(0xffD02127); + default: + return AppColors.greyColor; + } + } + + String _formatDate(String? dateString) { + if (dateString == null) return ''; + try { + final date = DateTime.parse(dateString); + return DateFormat('MMM dd, yyyy').format(date); + } catch (e) { + return dateString; + } + } + + Widget _buildLoadingShimmer() { + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: 3, + separatorBuilder: (_, __) => SizedBox(height: 12.h), + itemBuilder: (context, index) { + return _buildOrderCard(GetCMCAllOrdersResponseModel(), isLoading: true); + }, + ); + } + + Widget _buildOrderCard(GetCMCAllOrdersResponseModel order, {bool isLoading = false}) { + final statusColor = _getStatusColor(order.statusId); + final canCancel = order.statusId == 1 || order.statusId == 2; + + return AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status and Date Row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h), + decoration: BoxDecoration( + color: statusColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8.r), + ), + child: (isLoading ? "Processing" : order.statusText ?? '') + .toText12( + color: statusColor, + fontWeight: FontWeight.w600, + ) + .toShimmer2(isShow: isLoading), + ), + SizedBox(width: 8.w), + (isLoading ? "Jan 15, 2024" : _formatDate(order.created)) + .toText12( + color: AppColors.textColorLight, + fontWeight: FontWeight.w500, + ) + .toShimmer2(isShow: isLoading), + ], + ), + + SizedBox(height: 16.h), + + // Request ID + Row( + children: [ + if (!isLoading) ...[ + "Request ID:".needTranslation.toText14( + color: AppColors.textColorLight, + weight: FontWeight.w500, + ), + SizedBox(width: 4.w), + ], + (isLoading ? "12345" : "${order.iD ?? '-'}").toText16(isBold: true).toShimmer2(isShow: isLoading), + ], + ), + + SizedBox(height: 12.h), + + // Chips for Hospital, Service, and Amount + Wrap( + spacing: 6.w, + runSpacing: 6.h, + children: [ + // Hospital + if (order.projectName != null || isLoading) + AppCustomChipWidget( + icon: AppAssets.hospital, + labelText: isLoading ? "Hospital Name" : order.projectName ?? '-', + ).toShimmer2(isShow: isLoading), + + // Service + if (order.serviceText != null || isLoading) + AppCustomChipWidget( + icon: AppAssets.servicesBottom, + labelText: isLoading ? "Service Name" : order.serviceText ?? '-', + ).toShimmer2(isShow: isLoading), + ], + ), + + // Cancel Button + if (canCancel || isLoading) ...[ + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: "Cancel Order".needTranslation, + onPressed: isLoading ? () {} : () => CmcUiSelectionHelper.showCancelConfirmationDialog(context: context, order: order), + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 10.r, + height: 44.h, + ).toShimmer2(isShow: isLoading), + ), + ], + ), + ] + ], + ), + ), + ); + } + + Widget _buildEmptyState() { + 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 CMC orders yet.".needTranslation, + isSmallWidget: true, + width: 62.w, + height: 62.h, + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: "CMC Orders".needTranslation, + isLeading: true, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Consumer( + builder: (context, viewModel, child) { + if (viewModel.isCmcOrdersLoading) { + return _buildLoadingShimmer(); + } + + if (viewModel.cmcOrdersList.isEmpty) { + return _buildEmptyState(); + } + + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: viewModel.cmcOrdersList.length, + separatorBuilder: (_, __) => SizedBox(height: 12.h), + itemBuilder: (context, index) { + final order = viewModel.cmcOrdersList.reversed.toList()[index]; + + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: _buildOrderCard(order), + ), + ), + ); + }, + ); + }, + ), + ], + ).paddingSymmetrical(24.w, 0), + ), + ); + } +} diff --git a/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart b/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart new file mode 100644 index 00000000..b6164d9d --- /dev/null +++ b/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart @@ -0,0 +1,515 @@ +import 'dart:developer'; + +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/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/route_extensions.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/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/cmc_create_new_order_req_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_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/comprehensive_checkup/widgets/cmc_ui_selection_helper.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:maps_launcher/maps_launcher.dart'; +import 'package:provider/provider.dart'; + +class CmcSelectionReviewPage extends StatefulWidget { + final GetCMCServicesResponseModel selectedService; + final HospitalsModel? preSelectedHospital; + + const CmcSelectionReviewPage({super.key, required this.selectedService, this.preSelectedHospital}); + + @override + State createState() => _CmcSelectionReviewPageState(); +} + +class _CmcSelectionReviewPageState extends State { + @override + void initState() { + super.initState(); + // Initialize ViewModel state with preselected hospital if provided + if (widget.preSelectedHospital != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + final hmgServicesViewModel = context.read(); + hmgServicesViewModel.setSelectedHospitalForOrder(widget.preSelectedHospital); + hmgServicesViewModel.setSelectedServiceForOrder(widget.selectedService); + }); + } + } + + @override + Widget build(BuildContext context) { + final appState = getIt.get(); + final isArabic = appState.isArabic(); + + return CollapsingListView( + title: "Summary".needTranslation, + bottomChild: _buildBottomButton(), + child: SingleChildScrollView( + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildOrderSummaryCard(isArabic), + SizedBox(height: 16.h), + _buildSelectedServiceCard(isArabic), + SizedBox(height: 16.h), + _buildPaymentSummary(), + ], + ), + ), + ); + } + + Widget _buildOrderSummaryCard(bool isArabic) { + return Consumer( + builder: (context, hmgServicesViewModel, child) { + final selectedHospital = hmgServicesViewModel.selectedHospitalForOrder; + final isLocationSelected = selectedHospital != null; + + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 16.r, + ), + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Select Hospital".needTranslation, + style: TextStyle( + fontSize: 16.f, + fontWeight: FontWeight.w700, + color: AppColors.blackColor, + letterSpacing: -0.5, + ), + ), + SizedBox(height: 12.h), + _buildHospitalSelector(isArabic, selectedHospital, isLocationSelected), + if (isLocationSelected) ...[ + SizedBox(height: 16.h), + _buildHospitalMap(selectedHospital), + ], + ], + ), + ); + }, + ); + } + + Widget _buildHospitalSelector(bool isArabic, HospitalsModel? selectedHospital, bool isLocationSelected) { + return InkWell( + onTap: _showHospitalSelectionBottomSheet, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 14.h), + decoration: BoxDecoration( + color: AppColors.bgScaffoldColor, + borderRadius: BorderRadius.circular(12.r), + border: Border.all( + color: AppColors.greyColor.withAlpha(51), + width: 1, + ), + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + isLocationSelected && selectedHospital != null + ? (isArabic ? (selectedHospital.nameN ?? selectedHospital.name ?? '') : (selectedHospital.name ?? '')) + : "Select Hospital".needTranslation, + style: TextStyle( + fontSize: 14.f, + fontWeight: isLocationSelected ? FontWeight.w600 : FontWeight.w400, + color: isLocationSelected ? AppColors.blackColor : AppColors.greyTextColor, + letterSpacing: -0.4, + ), + ), + ], + ), + ), + Icon( + Icons.keyboard_arrow_down, + color: AppColors.greyTextColor, + size: 24.h, + ), + ], + ), + ), + ); + } + + Widget _buildHospitalMap(HospitalsModel selectedHospital) { + final String lat = selectedHospital.latitude ?? "0.0"; + final String lng = selectedHospital.longitude ?? "0.0"; + + log("selectedHospital: $lng and $lat"); + + if (lat == "0.0" || lng == "0.0") return SizedBox.shrink(); + + final String staticMapUrl = + "https://maps.googleapis.com/maps/api/staticmap?center=$lat,$lng&zoom=16&size=600x300&maptype=roadmap&markers=color:red%7C$lat,$lng&key=AIzaSyCyDbWUM9d_sBUGIE8PcuShzPaqO08NSC8"; + + return Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(12.r), + child: Image.network( + staticMapUrl, + height: 200.h, + width: double.infinity, + fit: BoxFit.cover, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Container( + height: 200.h, + decoration: BoxDecoration( + color: AppColors.bgScaffoldColor, + borderRadius: BorderRadius.circular(12.r), + ), + child: Center( + child: CircularProgressIndicator( + color: AppColors.primaryRedColor, + ), + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + return Container( + height: 200.h, + decoration: BoxDecoration( + color: AppColors.bgScaffoldColor, + borderRadius: BorderRadius.circular(12.r), + ), + child: Center( + child: Icon( + Icons.error_outline, + size: 48.h, + color: AppColors.greyTextColor, + ), + ), + ); + }, + ), + ), + Positioned( + bottom: 12.h, + right: 12.w, + child: InkWell( + onTap: () => _launchDirections(selectedHospital), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h), + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(1000.r), + boxShadow: [ + BoxShadow( + color: Color.fromARGB(26, 0, 0, 0), + blurRadius: 8, + offset: Offset(0, 2), + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.directions_icon, + width: 16.w, + height: 16.h, + ), + SizedBox(width: 6.w), + Text( + "Get Directions".needTranslation, + style: TextStyle( + fontSize: 12.f, + fontWeight: FontWeight.w600, + color: AppColors.blackColor, + letterSpacing: -0.4, + ), + ), + ], + ), + ), + ), + ), + ], + ); + } + + Widget _buildSelectedServiceCard(bool isArabic) { + final serviceName = isArabic ? (widget.selectedService.textN ?? widget.selectedService.text ?? '') : (widget.selectedService.text ?? ''); + final price = widget.selectedService.priceTotal ?? 0.0; + + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 16.r, + ), + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Selected Service".needTranslation.toText14( + weight: FontWeight.w600, + color: AppColors.greyTextColor, + letterSpacing: -0.4, + ), + SizedBox(height: 6.h), + Row( + children: [ + Expanded(child: serviceName.toText16(weight: FontWeight.w700, color: AppColors.blackColor, letterSpacing: -0.5)), + ], + ), + ], + ), + ); + } + + Widget _buildPaymentSummary() { + // Use selected service from widget + final service = widget.selectedService; + + log("service: ${service.toJson()}"); + + final double amountBeforeTax = service.price ?? 0.0; + final double taxAmount = service.priceVAT ?? 0.0; + final double totalAmount = service.priceTotal ?? (amountBeforeTax + taxAmount); + + 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 (use label VAT 15% if desired) + 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( + totalAmount.toString().toText24(isBold: true), + AppColors.blackColor, + 17, + isSaudiCurrency: true, + ), + ], + ).paddingSymmetrical(24.h, 0.h), + + SizedBox(height: 16.h), + ], + ); + }), + ); + } + + Widget _buildBottomButton() { + return Consumer( + builder: (context, hmgServicesViewModel, child) { + final isLocationSelected = hmgServicesViewModel.selectedHospitalForOrder != null; + + return SafeArea( + top: false, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + decoration: BoxDecoration( + color: AppColors.whiteColor, + boxShadow: [ + BoxShadow( + color: Color.fromARGB(13, 0, 0, 0), + blurRadius: 8, + offset: Offset(0, -2), + ), + ], + ), + child: CustomButton( + text: "Confirm".needTranslation, + onPressed: () { + isLocationSelected ? _handleConfirm() : null; + }, + textColor: AppColors.whiteColor, + backgroundColor: isLocationSelected ? AppColors.successColor : AppColors.greyColor, + borderRadius: 12.r, + borderColor: Colors.transparent, + borderWidth: 0, + padding: EdgeInsets.symmetric(vertical: 14.h), + ), + ), + ); + }, + ); + } + + void _showHospitalSelectionBottomSheet() { + CmcUiSelectionHelper.showHospitalSelectionBottomSheet(context: context, onHospitalSelected: (hospital) => context.pop()); + } + + void _launchDirections(HospitalsModel selectedHospital) { + final double lat = double.parse(selectedHospital.latitude ?? "0.0"); + final double lng = double.parse(selectedHospital.longitude ?? "0.0"); + + if (lat != 0.0 && lng != 0.0) { + MapsLauncher.launchCoordinates( + lat, + lng, + selectedHospital.name ?? "Hospital", + ); + } + } + + showSuccessBottomSheet(int requestId, HmgServicesViewModel hmgServicesViewModel) { + return showCommonBottomSheetWithoutHeight( + context, + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + children: [ + Utils.getSuccessWidget(loadingText: "Your request has been successfully submitted.".needTranslation), + Row( + children: [ + "Here is your request #: ".needTranslation.toText14( + color: AppColors.textColorLight, + weight: FontWeight.w500, + ), + SizedBox(width: 4.w), + ("$requestId").toText16(isBold: true), + ], + ), + SizedBox(height: 24.h), + Row( + children: [ + Expanded( + child: CustomButton( + height: 56.h, + text: LocaleKeys.ok.tr(), + onPressed: () { + context.pop(); + context.pop(); + hmgServicesViewModel.getAllCmcOrders(); + }, + textColor: AppColors.whiteColor, + ), + ), + ], + ), + ], + ), + ), + isCloseButtonVisible: false, + isDismissible: false, + isFullScreen: false, + ); + } + + void _handleConfirm() { + final hmgServicesViewModel = context.read(); + final selectedHospital = hmgServicesViewModel.selectedHospitalForOrder; + + if (selectedHospital == null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text("Please select a hospital to continue".needTranslation), + backgroundColor: AppColors.errorColor, + ), + ); + return; + } + + final selectedService = widget.selectedService; + return showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: "Are you sure you want to submit this request?".needTranslation, + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + Navigator.pop(context); + LoaderBottomSheet.showLoader(); + + // Create the services list + final servicesList = [ + PatientERCMCInsertServicesList( + recordID: selectedService.iD, + serviceID: selectedService.serviceID, + selectedServiceName: selectedService.text, + selectedServiceNameAR: selectedService.textN, + price: selectedService.price, + vAT: selectedService.priceVAT, + totalPrice: selectedService.priceTotal, + ), + ]; + + await hmgServicesViewModel.addCmcOrder( + projectID: selectedHospital.mainProjectID ?? 0, + orderServiceID: selectedService.orderServiceID ?? 3, + services: servicesList, + onSuccess: (requestId) { + LoaderBottomSheet.hideLoader(); + showSuccessBottomSheet(requestId, hmgServicesViewModel); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + // showCommonBottomSheetWithoutHeight(context, child: Utils.getErrorWidget(loadingText: err), callBackFunc: () {}); + }, + ); + }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } +} diff --git a/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart b/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart new file mode 100644 index 00000000..5529b93c --- /dev/null +++ b/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart @@ -0,0 +1,405 @@ +import 'dart:async'; + +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/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/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; +import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/cmc_order_detail_page.dart'; +import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/cmc_selection_review_page.dart'; +import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.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/media_viewer/full_screen_image_viewer.dart'; +import 'package:hmg_patient_app_new/widgets/radio_list_tile_widget.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +class ComprehensiveCheckupPage extends StatefulWidget { + const ComprehensiveCheckupPage({super.key}); + + @override + State createState() => _ComprehensiveCheckupPageState(); +} + +class _ComprehensiveCheckupPageState extends State { + int? _selectedServiceId; + GetCMCServicesResponseModel? _selectedService; + + @override + void initState() { + super.initState(); + final HmgServicesViewModel hmgServicesViewModel = context.read(); + final AppState appState = getIt.get(); + + scheduleMicrotask(() async { + final user = appState.getAuthenticatedUser(); + if (user != null) { + await hmgServicesViewModel.getAllCmcOrders(); + await hmgServicesViewModel.getAllCmcServices(patientID: user.patientId ?? 0); + } + }); + } + + GetCMCAllOrdersResponseModel? _getPendingOrder(List orders) { + if (orders.isEmpty) return null; + + // Find pending or processing orders (status 1 or 2) + for (var order in orders) { + if (order.statusId == 1 || order.statusId == 2) { + return order; + } + } + + return null; + } + + Widget _buildPendingOrderCard(GetCMCAllOrdersResponseModel order) { + int status = order.statusId ?? 0; + String statusDisp = order.statusText ?? ""; + Color statusColor; + + if (status == 1) { + // pending + statusColor = AppColors.statusPendingColor; + } else if (status == 2) { + // processing + statusColor = AppColors.statusProcessingColor; + } else if (status == 3) { + // completed + statusColor = AppColors.statusCompletedColor; + } else { + // cancel / rejected + statusColor = AppColors.statusRejectedColor; + } + + final canCancel = order.statusId == 1 || order.statusId == 2; + + return Container( + width: double.infinity, + margin: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status and Date Row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h), + decoration: BoxDecoration( + color: statusColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8.r), + ), + child: statusDisp.toText12( + color: statusColor, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(width: 8.w), + if (order.created != null) + DateFormat('MMM dd, yyyy').format(DateTime.parse(order.created!)).toText12( + color: AppColors.textColorLight, + fontWeight: FontWeight.w500, + ), + ], + ), + + SizedBox(height: 16.h), + + // Request ID + Row( + children: [ + "Request ID:".needTranslation.toText14(color: AppColors.textColorLight, weight: FontWeight.w500), + SizedBox(width: 4.w), + "${order.iD ?? '-'}".toText16(isBold: true), + ], + ), + + SizedBox(height: 12.h), + + // Chips for Hospital, Service, and Amount + Wrap( + spacing: 6.w, + runSpacing: 6.h, + children: [ + // Hospital + if (order.projectName != null) + AppCustomChipWidget( + icon: AppAssets.hospital, + labelText: order.projectName ?? '-', + ), + + // Service + if (order.serviceText != null) + AppCustomChipWidget( + icon: AppAssets.file_icon, + labelText: order.serviceText ?? '-', + ), + ], + ), + + SizedBox(height: 16.h), + + // Info message + Container( + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + color: AppColors.infoBannerBgColor, + borderRadius: BorderRadius.circular(10.r), + border: Border.all( + color: AppColors.infoBannerBorderColor, + width: 1, + ), + ), + child: Row( + children: [ + Icon( + Icons.info_outline, + size: 20.w, + color: AppColors.infoBannerIconColor, + ), + SizedBox(width: 8.w), + Expanded( + child: "You have a pending order. Please wait for it to be processed.".needTranslation.toText12( + color: AppColors.infoBannerTextColor, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + if (canCancel) ...[ + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: "Cancel Order".needTranslation, + onPressed: () => CmcUiSelectionHelper.showCancelConfirmationDialog(context: context, order: order), + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 10.r, + height: 44.h, + ), + ), + ], + ), + ] + ], + ), + ), + ); + } + + Widget _buildServiceSelectionList(List services) { + if (services.isEmpty) { + return Center( + child: Padding( + padding: EdgeInsets.all(24.h), + child: Text( + 'No services available'.needTranslation, + style: TextStyle( + fontSize: 16.h, + color: AppColors.greyTextColor, + ), + ), + ), + ); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + Text( + 'Select a Service'.needTranslation, + style: TextStyle( + fontSize: 20.h, + fontWeight: FontWeight.w700, + color: AppColors.blackColor, + letterSpacing: -0.8, + ), + ).paddingOnly(left: 16.w, right: 16.w), + ListView.builder( + padding: EdgeInsets.zero, + itemCount: services.length, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + final service = services[index]; + final serviceName = service.text ?? service.textN ?? ''; + final price = service.priceTotal ?? 0.0; + return RadioListTileWidget( + value: service.iD ?? 0, + groupValue: _selectedServiceId, + title: serviceName, + subtitleWidget: Utils.getPaymentAmountWithSymbol( + isExpanded: false, + price.toString().toText14(), + AppColors.blackColor, + 14, + isSaudiCurrency: true, + ), + onChanged: (value) { + setState(() { + _selectedServiceId = value; + _selectedService = service; + }); + }, + ); + }, + ), + // Illustration image below the services list similar to the old implementation + SizedBox(height: 12.h), + Builder(builder: (context) { + final appStateLocal = getIt.get(); + final String imagePath = appStateLocal.isArabic() ? AppAssets.comprehensiveCheckupAr : AppAssets.comprehensiveCheckupEn; + return Stack( + children: [ + Image.asset( + imagePath, + width: double.infinity, + fit: BoxFit.cover, + ).paddingAll(16.w), + Align( + alignment: Alignment.topRight, + child: Container( + decoration: BoxDecoration( + color: Color.fromARGB(51, 0, 0, 0), + borderRadius: BorderRadius.circular(1000.r), + ), + margin: EdgeInsets.all(16.h), + child: IconButton( + icon: Icon( + Icons.zoom_in, + color: Colors.white, + size: 26.w, + ), + padding: EdgeInsets.all(10.h), + onPressed: () => _showFullScreenImage(context, imagePath, isSvg: false), + ), + ), + ), + ], + ); + }), + ], + ); + } + + void _proceedWithSelectedService() { + if (_selectedService != null) { + final hmgServicesViewModel = context.read(); + + // Store selected service in ViewModel + hmgServicesViewModel.setSelectedServiceForOrder(_selectedService); + hmgServicesViewModel.getHospitalsList(); + // Show hospital selection bottom sheet using common helper + CmcUiSelectionHelper.showHospitalSelectionBottomSheet( + context: context, + onHospitalSelected: (hospital) { + Navigator.of(context).pushReplacement( + CustomPageRoute( + page: CmcSelectionReviewPage(selectedService: _selectedService!, preSelectedHospital: hospital), + direction: AxisDirection.left, + ), + ); + }, + ); + } + } + + Widget _buildLoadingShimmer() { + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: EdgeInsets.all(16.w), + itemCount: 10, + separatorBuilder: (_, __) => SizedBox(height: 12.h), + itemBuilder: (context, index) { + return Shimmer.fromColors( + baseColor: Colors.grey[300]!, + highlightColor: Colors.grey[100]!, + child: Container( + height: 80.h, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10.r), + ), + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: "Comprehensive Checkup".needTranslation, + history: () => Navigator.of(context).push(CustomPageRoute(page: CmcOrderDetailPage(), direction: AxisDirection.up)), + bottomChild: Consumer( + builder: (context, hmgServicesViewModel, child) { + if (hmgServicesViewModel.isCmcOrdersLoading || hmgServicesViewModel.isCmcServicesLoading) return SizedBox.shrink(); + final pendingOrder = _getPendingOrder(hmgServicesViewModel.cmcOrdersList); + if (pendingOrder == null && _selectedServiceId != null) { + return SafeArea( + top: false, + child: Padding( + padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 12.h), + child: CustomButton( + borderWidth: 0, + text: "Next".needTranslation, + onPressed: _proceedWithSelectedService, + textColor: AppColors.whiteColor, + borderRadius: 12.r, + borderColor: Colors.transparent, + padding: EdgeInsets.symmetric(vertical: 14.h), + ), + ), + ); + } + return SizedBox.shrink(); + }, + ), + child: Consumer( + builder: (context, hmgServicesViewModel, child) { + if (hmgServicesViewModel.isCmcOrdersLoading || hmgServicesViewModel.isCmcServicesLoading) { + return _buildLoadingShimmer(); + } + final pendingOrder = _getPendingOrder(hmgServicesViewModel.cmcOrdersList); + if (pendingOrder != null) { + return _buildPendingOrderCard(pendingOrder); + } else { + return _buildServiceSelectionList(hmgServicesViewModel.cmcServicesList); + } + }, + ), + ); + } + + void _showFullScreenImage(BuildContext context, String path, {bool isSvg = false}) { + Navigator.of(context).push(MaterialPageRoute(builder: (_) => FullScreenImageViewer(isSvg: isSvg, path: path))); + } +} diff --git a/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_bottom_sheet_body.dart b/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_bottom_sheet_body.dart new file mode 100644 index 00000000..98e91b87 --- /dev/null +++ b/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_bottom_sheet_body.dart @@ -0,0 +1,111 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/enums.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/features/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/widgets/cmc_hospital_list_item.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +class CmcHospitalBottomSheetBody extends StatelessWidget { + final Function(HospitalsModel) onHospitalSelected; + + const CmcHospitalBottomSheetBody({super.key, required this.onHospitalSelected}); + + Widget _buildLoadingShimmer() { + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: 4, + separatorBuilder: (_, __) => SizedBox(height: 12.h), + itemBuilder: (context, index) { + return Shimmer.fromColors( + baseColor: Colors.grey[300]!, + highlightColor: Colors.grey[100]!, + child: Container( + height: 80.h, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10.r), + ), + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + final appState = getIt.get(); + final bool isArabic = appState.isArabic(); + final bool isLocationEnabled = (appState.userLat != 0) && (appState.userLong != 0); + + return Consumer( + builder: (BuildContext context, HmgServicesViewModel hmgServicesViewModel, Widget? child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Choose your preferred hospital for the service".needTranslation.toText14( + weight: FontWeight.w400, + color: AppColors.greyTextColor, + letterSpacing: -0.4, + ), + SizedBox(height: 16.h), + TextInputWidget( + labelText: LocaleKeys.search.tr(), + hintText: LocaleKeys.searchHospital.tr(), + onChange: (value) { + hmgServicesViewModel.filterHospitalsByString(value ?? '', isArabic); + }, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + isAllowLeadingIcon: true, + selectionType: SelectionTypeEnum.search, + padding: EdgeInsets.symmetric(vertical: ResponsiveExtension(10).h, horizontal: ResponsiveExtension(15).h), + ), + ], + ), + SizedBox(height: 8.h), + SizedBox( + height: MediaQuery.of(context).size.height * 0.4, + child: hmgServicesViewModel.isHospitalListLoading + ? _buildLoadingShimmer() + : hmgServicesViewModel.filteredHospitalsList.isEmpty + ? Center( + child: "No hospitals Found".needTranslation.toText16(weight: FontWeight.w500, color: AppColors.greyTextColor), + ) + : ListView.separated( + itemCount: hmgServicesViewModel.filteredHospitalsList.length, + separatorBuilder: (context, index) => SizedBox(height: 12.h), + itemBuilder: (context, index) { + final hospital = hmgServicesViewModel.filteredHospitalsList[index]; + return CmcHospitalListItem( + hospital: hospital, + isLocationEnabled: isLocationEnabled, + onPress: () { + hmgServicesViewModel.setSelectedHospital(hospital); + onHospitalSelected(hospital); + }, + ); + }, + ), + ), + ], + ); + }, + ); + } +} diff --git a/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_list_item.dart b/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_list_item.dart new file mode 100644 index 00000000..39d6d7ce --- /dev/null +++ b/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_list_item.dart @@ -0,0 +1,126 @@ +import 'dart:developer'; + +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/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/hospital_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; + +class CmcHospitalListItem extends StatelessWidget { + final HospitalsModel hospital; + final VoidCallback onPress; + final bool isLocationEnabled; + + const CmcHospitalListItem({ + super.key, + required this.hospital, + required this.onPress, + this.isLocationEnabled = false, + }); + + @override + Widget build(BuildContext context) { + final appState = getIt.get(); + final bool isArabic = appState.isArabic(); + final String hospitalName = isArabic ? (hospital.nameN ?? hospital.name ?? '') : (hospital.name ?? ''); + + return InkWell( + onTap: onPress, + child: DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.h, + children: [ + _buildHospitalName(hospitalName), + _buildDistanceInfo(), + ], + ), + ), + Transform.flip( + flipX: isArabic, + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ), + ); + } + + Widget _buildHospitalName(String hospitalName) { + return Row( + children: [ + Utils.buildSvgWithAssets( + icon: (hospital.isHMC == true) ? AppAssets.hmc : AppAssets.hmg, + ).paddingOnly(right: 10), + Expanded( + child: Text( + hospitalName, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: AppColors.blackColor, + ), + ), + ) + ], + ); + } + + Widget _buildDistanceInfo() { + log("hospital: ${hospital.distanceInKilometers}"); + final distanceText = hospital.distanceInKilometers != null ? hospital.distanceInKilometers!.toStringAsFixed(1) : "0"; + + return Row( + spacing: 4.w, + children: [ + Visibility( + visible: (hospital.distanceInKilometers != null && hospital.distanceInKilometers! > 0), + child: AppCustomChipWidget( + labelText: "$distanceText km".needTranslation, + icon: AppAssets.location_red, + iconColor: AppColors.errorColor, + backgroundColor: AppColors.secondaryLightRedColor, + textColor: AppColors.errorColor, + ), + ), + Visibility( + visible: (hospital.distanceInKilometers == null || hospital.distanceInKilometers == 0), + child: AppCustomChipWidget( + labelText: " Distance not available".needTranslation, + textColor: AppColors.blackColor, + ), + ), + // Visibility( + // visible: !isLocationEnabled, + // child: AppCustomChipWidget( + // labelText: "Location turned off".needTranslation, + // deleteIcon: AppAssets.location_unavailable, + // deleteIconSize: Size(9.w, 12.h), + // textColor: AppColors.blackColor, + // ), + // ), + ], + ); + } +} diff --git a/lib/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.dart b/lib/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.dart new file mode 100644 index 00000000..908aac91 --- /dev/null +++ b/lib/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/widgets/cmc_hospital_bottom_sheet_body.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:provider/provider.dart'; +import 'package:easy_localization/easy_localization.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/route_extensions.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/order_update_req_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.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/loader/bottomsheet_loader.dart'; + +class CmcUiSelectionHelper { + static void showHospitalSelectionBottomSheet({ + required BuildContext context, + required Function(dynamic) onHospitalSelected, + }) { + final hmgServicesViewModel = context.read(); + + showCommonBottomSheetWithoutHeight( + context, + title: "Select Hospital".needTranslation, + child: CmcHospitalBottomSheetBody( + onHospitalSelected: (hospital) { + hmgServicesViewModel.setSelectedHospitalForOrder(hospital); + onHospitalSelected(hospital); + }, + ), + ); + } + + static void showCancelConfirmationDialog({ + required BuildContext context, + required GetCMCAllOrdersResponseModel order, + }) { + final HmgServicesViewModel hmgServicesViewModel = context.read(); + + return showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: "Are you sure you want to cancel this order?".needTranslation, + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + Navigator.pop(context); + LoaderBottomSheet.showLoader(); + + final requestModel = OrderUpdateRequestModel( + presOrderID: order.iD, + rejectionReason: "Cancelled by user", + presOrderStatus: 4, // Cancelled status + editedBy: 3, + ); + + await hmgServicesViewModel.updateCmcPresOrder( + requestModel: requestModel, + onSuccess: (_) async { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + children: [ + Utils.getSuccessWidget(loadingText: "Order has been cancelled successfully".needTranslation), + SizedBox(height: 24.h), + Row( + children: [ + Expanded( + child: CustomButton( + height: 56.h, + text: LocaleKeys.ok.tr(), + onPressed: () { + context.pop(); + context.pop(); + hmgServicesViewModel.getAllCmcOrders(); + }, + textColor: AppColors.whiteColor, + ), + ), + ], + ), + ], + ), + ), + isCloseButtonVisible: false, + isDismissible: false, + isFullScreen: false, + ); + }, + onError: (error) { + LoaderBottomSheet.hideLoader(); + }, + ); + }, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } +} diff --git a/lib/presentation/contact_us/contact_us.dart b/lib/presentation/contact_us/contact_us.dart index 6890fb4d..d7ea9c56 100644 --- a/lib/presentation/contact_us/contact_us.dart +++ b/lib/presentation/contact_us/contact_us.dart @@ -9,7 +9,9 @@ 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/contact_us/contact_us_view_model.dart'; +import 'package:hmg_patient_app_new/features/contact_us/models/feedback_type.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart'; import 'package:hmg_patient_app_new/presentation/contact_us/find_us_page.dart'; import 'package:hmg_patient_app_new/presentation/contact_us/live_chat_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -51,7 +53,17 @@ class ContactUs extends StatelessWidget { AppAssets.checkin_location_icon, LocaleKeys.feedback.tr(), "Provide your feedback on our services".needTranslation, - ), + ).onPress(() { + contactUsViewModel.setSelectedFeedbackType( + FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد'), + ); + Navigator.pop(context); + Navigator.of(context).push( + CustomPageRoute( + page: FeedbackPage(), + ), + ); + }), SizedBox(height: 16.h), checkInOptionCard( AppAssets.checkin_location_icon, @@ -59,6 +71,7 @@ class ContactUs extends StatelessWidget { "Live chat option with HMG".needTranslation, ).onPress(() { locationUtils.getCurrentLocation(onSuccess: (value) { + contactUsViewModel.getLiveChatProjectsList(); Navigator.pop(context); Navigator.of(context).push( CustomPageRoute( diff --git a/lib/presentation/contact_us/feedback_page.dart b/lib/presentation/contact_us/feedback_page.dart new file mode 100644 index 00000000..db7c218d --- /dev/null +++ b/lib/presentation/contact_us/feedback_page.dart @@ -0,0 +1,400 @@ +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/dependencies.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/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; +import 'package:hmg_patient_app_new/features/contact_us/models/feedback_type.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart'; +import 'package:hmg_patient_app_new/presentation/contact_us/widgets/feedback_appointment_selection.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/custom_tab_bar.dart'; +import 'package:hmg_patient_app_new/widgets/image_picker.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.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 FeedbackPage extends StatelessWidget { + FeedbackPage({super.key}); + + late ContactUsViewModel contactUsViewModel; + late MedicalFileViewModel medicalFileViewModel; + + final TextEditingController subjectTextController = TextEditingController(); + final TextEditingController messageTextController = TextEditingController(); + + @override + Widget build(BuildContext context) { + contactUsViewModel = Provider.of(context, listen: false); + medicalFileViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Consumer(builder: (context, contactUsVM, child) { + return Column( + children: [ + Expanded( + child: CollapsingListView( + isLeading: Navigator.canPop(context), + title: LocaleKeys.feedback.tr(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + CustomTabBar( + activeTextColor: AppColors.primaryRedColor, + activeBackgroundColor: AppColors.primaryRedColor.withValues(alpha: .1), + tabs: [ + CustomTabBarModel(null, "Send".needTranslation), + CustomTabBarModel(null, "Status".needTranslation), + ], + onTabChange: (index) { + contactUsViewModel.setIsSendFeedbackTabSelected(index == 0); + }, + ).paddingSymmetrical(24.h, 0.h), + getSelectedTabWidget(context).paddingSymmetrical(24.h, 16.w), + ], + ), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + customBorder: BorderRadius.only( + topLeft: Radius.circular(24.h), + topRight: Radius.circular(24.h), + ), + hasShadow: true, + ), + child: CustomButton( + text: LocaleKeys.submit.tr(context: context), + onPressed: () async { + if (subjectTextController.text.isEmpty) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: LocaleKeys.emptySubject.tr(context: context)), + ); + return; + } + if (messageTextController.text.isEmpty) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: LocaleKeys.emptyMessage.tr(context: context)), + ); + return; + } + LoaderBottomSheet.showLoader(loadingText: "Sending Feedback...".needTranslation); + contactUsViewModel.insertCOCItem( + subject: subjectTextController.text, + message: messageTextController.text, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + subjectTextController.clear(); + messageTextController.clear(); + contactUsViewModel.setPatientFeedbackSelectedAppointment(null); + showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr(context: context)), callBackFunc: () { + Navigator.pop(context); + }); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getSuccessWidget(loadingText: err), + ); + }); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.feedback, + iconColor: AppColors.whiteColor, + iconSize: 20.h, + ).paddingSymmetrical(24.h, 24.h), + ), + ], + ); + }), + ); + } + + Widget getSelectedTabWidget(BuildContext context) { + if (contactUsViewModel.isSendFeedbackTabSelected) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.likeToHear.tr().toText14(weight: FontWeight.w500), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 16.r, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.ask_doctor_icon, width: 24.w, height: 24.h, iconColor: AppColors.greyTextColor), + SizedBox(width: 12.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.feedbackType.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), + (getIt.get().isArabic() ? contactUsViewModel.selectedFeedbackType.nameAR : contactUsViewModel.selectedFeedbackType.nameEN) + .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), + ], + ).onPress(() { + showCommonBottomSheetWithoutHeight(context, + title: "Select Feedback Type".needTranslation, + child: Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24), + child: ListView.builder( + itemCount: contactUsViewModel.feedbackTypeList.length, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.only(top: 8, bottom: 8), + shrinkWrap: true, + itemBuilder: (innerContext, index) { + return Theme( + data: Theme.of(context).copyWith( + listTileTheme: ListTileThemeData(horizontalTitleGap: 4), + ), + child: RadioListTile( + title: Text( + getIt.get().isArabic() ? contactUsViewModel.feedbackTypeList[index].nameAR : contactUsViewModel.feedbackTypeList[index].nameEN, + style: TextStyle( + fontSize: 16.h, + fontWeight: FontWeight.w500, + ), + ), + value: contactUsViewModel.feedbackTypeList[index], + fillColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return AppColors.primaryRedColor; + } + return Color(0xffEEEEEE); + }), + contentPadding: EdgeInsets.only(left: 12.h, right: 12.h), + groupValue: contactUsViewModel.selectedFeedbackType, + onChanged: (FeedbackType? newValue) async { + Navigator.pop(context); + contactUsViewModel.setSelectedFeedbackType(newValue!); + if (contactUsViewModel.selectedFeedbackType.id == 1) { + LoaderBottomSheet.showLoader(loadingText: "Loading appointments list...".needTranslation); + await medicalFileViewModel.getPatientMedicalReportAppointmentsList(onSuccess: (val) async { + LoaderBottomSheet.hideLoader(); + bool? value = await Navigator.of(context).push( + CustomPageRoute( + page: FeedbackAppointmentSelection(), + fullScreenDialog: true, + direction: AxisDirection.down, + ), + ); + if (value != null) { + // showConfirmRequestMedicalReportBottomSheet(); + } + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "You do not have any appointments to submit a feedback.".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + contactUsViewModel.setPatientFeedbackSelectedAppointment(null); + } + }, + ), + ); + }, + ), + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true); + }), + ]), + ), + ), + if (contactUsViewModel.patientFeedbackSelectedAppointment != null) ...[ + SizedBox(height: 16.h), + "Selected Appointment:".needTranslation.toText16(isBold: true), + SizedBox(height: 8.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: false, + ), + padding: EdgeInsets.all(16.h), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.network( + contactUsViewModel.patientFeedbackSelectedAppointment!.doctorImageURL!, + width: 63.h, + height: 63.h, + fit: BoxFit.cover, + ).circle(100).toShimmer2(isShow: false), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (contactUsViewModel.patientFeedbackSelectedAppointment!.doctorNameObj!).toText16(isBold: true, maxlines: 1).toShimmer2(isShow: false), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget(labelText: contactUsViewModel.patientFeedbackSelectedAppointment!.clinicName!).toShimmer2(isShow: false), + AppCustomChipWidget(labelText: contactUsViewModel.patientFeedbackSelectedAppointment!.projectName!).toShimmer2(isShow: false), + AppCustomChipWidget( + icon: AppAssets.appointment_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(contactUsViewModel.patientFeedbackSelectedAppointment!.appointmentDate), false), + ).toShimmer2(isShow: false), + ], + ), + ], + ), + ), + ], + ), + ), + ], + SizedBox(height: 16.h), + TextInputWidget( + labelText: "Subject".needTranslation, + hintText: "Enter subject here".needTranslation, + controller: subjectTextController, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), + ), + SizedBox(height: 16.h), + TextInputWidget( + labelText: "Message".needTranslation, + hintText: "Enter message here".needTranslation, + controller: messageTextController, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + isMultiline: true, + keyboardType: TextInputType.text, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), + ), + SizedBox(height: 16.h), + CustomButton( + text: LocaleKeys.selectAttachment.tr(context: context), + onPressed: () async { + ImageOptions.showImageOptionsNew( + context, + true, + (String image, file) { + print(image); + print(file); + Navigator.pop(context); + contactUsViewModel.addFeedbackAttachment(image); + }, + ); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 10.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: isTablet || isFoldable ? 46.h : 40.h, + icon: AppAssets.file_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ), + SizedBox(height: 16.h), + contactUsViewModel.feedbackAttachmentList.isNotEmpty + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + hasShadow: false, + ), + child: ListView.builder( + padding: EdgeInsets.all(16.h), + shrinkWrap: true, + itemCount: contactUsViewModel.feedbackAttachmentList.length, + itemBuilder: (BuildContext context, int index) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + Icons.attach_file, + color: Color(0xff2B353E), + ), + SizedBox(width: 8.w), + "Image ${index + 1}".toText14().paddingOnly(bottom: 8.h), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.cancel_circle_icon).onPress(() { + contactUsViewModel.removeFeedbackAttachment(contactUsViewModel.feedbackAttachmentList[index]); + }), + ], + ); + }, + ), + ) + : SizedBox.shrink(), + ], + ); + } else { + return Container(); + } + } +} diff --git a/lib/presentation/contact_us/live_chat_page.dart b/lib/presentation/contact_us/live_chat_page.dart index aced6782..7cbdee34 100644 --- a/lib/presentation/contact_us/live_chat_page.dart +++ b/lib/presentation/contact_us/live_chat_page.dart @@ -1,27 +1,176 @@ 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/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/contact_us/contact_us_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.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:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; class LiveChatPage extends StatelessWidget { - const LiveChatPage({super.key}); + LiveChatPage({super.key}); + + String chatURL = ""; + + late AppState appState; @override Widget build(BuildContext context) { + appState = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - body: Column( - children: [ - Expanded( - child: CollapsingListView( - title: LocaleKeys.liveChat.tr(), - child: SingleChildScrollView(), + body: Consumer(builder: (context, contactUsVM, child) { + return Column( + children: [ + Expanded( + child: CollapsingListView( + title: LocaleKeys.liveChat.tr(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + ListView.separated( + padding: EdgeInsets.only(top: 16.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: contactUsVM.isLiveChatProjectsListLoading ? 5 : contactUsVM.liveChatProjectsList.length, + itemBuilder: (context, index) { + return contactUsVM.isLiveChatProjectsListLoading + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.network( + "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", + width: 63.h, + height: 63.h, + fit: BoxFit.cover, + ).circle(100).toShimmer2(isShow: true), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Dr John Smith".toText16(isBold: true).toShimmer2(isShow: true), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h), + AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h), + ], + ), + ], + ), + ), + ], + ), + ], + ), + ), + ), + ).paddingSymmetrical(24.h, 0.h) + : 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: DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: contactUsVM.selectedLiveChatProjectIndex == index ? AppColors.primaryRedColor : AppColors.whiteColor, + borderRadius: 16.r, + hasShadow: false, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ("${appState.isArabic() ? contactUsVM.liveChatProjectsList[index].projectNameN! : contactUsVM.liveChatProjectsList[index].projectName!}\n${contactUsVM.liveChatProjectsList[index].distanceInKilometers!} KM") + .needTranslation + .toText14(isBold: true, color: contactUsVM.selectedLiveChatProjectIndex == index ? AppColors.whiteColor : AppColors.textColor), + Transform.flip( + flipX: getIt.get().isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: contactUsVM.selectedLiveChatProjectIndex == index ? AppColors.whiteColor : AppColors.textColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).onPress(() { + contactUsVM.setSelectedLiveChatProjectIndex(index); + chatURL = + "https://chat.hmg.com/Index.aspx?Name=${appState.getAuthenticatedUser()!.firstName}&PatientID=${appState.getAuthenticatedUser()!.patientId}&MobileNo=${appState.getAuthenticatedUser()!.mobileNumber}&Language=${appState.isArabic() ? 'ar' : 'en'}&WorkGroup=${contactUsVM.liveChatProjectsList[index].value}"; + debugPrint("Chat URL: $chatURL"); + }), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + SizedBox(height: 24.h), + ], + ), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: CustomButton( + text: LocaleKeys.liveChat.tr(context: context), + onPressed: () async { + Uri uri = Uri.parse(chatURL); + launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: ""); + }, + backgroundColor: contactUsVM.selectedLiveChatProjectIndex == -1 ? AppColors.greyColor : AppColors.primaryRedColor, + borderColor: contactUsVM.selectedLiveChatProjectIndex == -1 ? AppColors.greyColor : AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + ).paddingSymmetrical(24.h, 24.h), ), - ), - Container() - ], - ), + ], + ); + }), ); } } diff --git a/lib/presentation/contact_us/widgets/feedback_appointment_selection.dart b/lib/presentation/contact_us/widgets/feedback_appointment_selection.dart new file mode 100644 index 00000000..ca040691 --- /dev/null +++ b/lib/presentation/contact_us/widgets/feedback_appointment_selection.dart @@ -0,0 +1,70 @@ +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/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:provider/provider.dart'; + +class FeedbackAppointmentSelection extends StatelessWidget { + FeedbackAppointmentSelection({super.key}); + + late MedicalFileViewModel medicalFileViewModel; + late ContactUsViewModel contactUsViewModel; + + @override + Widget build(BuildContext context) { + medicalFileViewModel = Provider.of(context, listen: false); + contactUsViewModel = Provider.of(context, listen: false); + return CollapsingListView( + title: LocaleKeys.feedback.tr(), + isClose: true, + child: Column( + children: [ + ListView.separated( + padding: EdgeInsets.only(top: 24.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: medicalFileViewModel.patientMedicalReportAppointmentHistoryList.length, + itemBuilder: (context, 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: AppointmentCard( + patientAppointmentHistoryResponseModel: medicalFileViewModel.patientMedicalReportAppointmentHistoryList[index], + myAppointmentsViewModel: Provider.of(context, listen: false), + bookAppointmentsViewModel: Provider.of(context, listen: false), + medicalFileViewModel: medicalFileViewModel, + contactUsViewModel: contactUsViewModel, + isLoading: false, + isFromHomePage: false, + isFromMedicalReport: true, + isForFeedback: true, + ), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + SizedBox(height: 24.h), + ], + ), + ); + } +} diff --git a/lib/presentation/e_referral/e_referral_page_home.dart b/lib/presentation/e_referral/e_referral_page_home.dart new file mode 100644 index 00000000..bacca476 --- /dev/null +++ b/lib/presentation/e_referral/e_referral_page_home.dart @@ -0,0 +1,97 @@ +import 'dart:ui'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.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/prescriptions/prescriptions_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/e_referral/new_referral.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:provider/provider.dart'; + +class EReferralPage extends StatefulWidget { + const EReferralPage({super.key}); + + @override + _EReferralPageState createState() => _EReferralPageState(); +} + +class _EReferralPageState extends State + { + + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + bool isNewReferral = true; + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title:"E Referral".needTranslation, + child: SingleChildScrollView( + child: Consumer(builder: (context, model, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + Row( + children: [ + CustomButton( + text: "New Referral".needTranslation, + onPressed: () { + isNewReferral =true; + setState(() { + + }); + }, + backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: "Search Referral".needTranslation, + onPressed: () { + isNewReferral =false; + }, + backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, + borderColor: model.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor, + textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 20.h), + isNewReferral ? NewEReferral() : SizedBox(), + ], + ); + }), + ), + ), + ); + + + + + } +} diff --git a/lib/presentation/e_referral/new_referral.dart b/lib/presentation/e_referral/new_referral.dart new file mode 100644 index 00000000..4ed9b8e6 --- /dev/null +++ b/lib/presentation/e_referral/new_referral.dart @@ -0,0 +1,172 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class NewEReferral extends StatefulWidget { + NewEReferral(); + + @override + _NewEReferralState createState() => _NewEReferralState(); +} + +class _NewEReferralState extends State with TickerProviderStateMixin { + late PageController _controller; + int _currentIndex = 0; + int pageSelected = 2; + + // CreateEReferralRequestModel createEReferralRequestModel = new CreateEReferralRequestModel(); + + @override + void initState() { + super.initState(); + _controller = new PageController(); + } + + @override + void dispose() { + super.dispose(); + } + + changePageViewIndex(pageIndex) { + _controller.jumpToPage(pageIndex); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + height: double.infinity, + child: Column( + children: [ + Container( + width: double.infinity, + padding: EdgeInsets.only(left: 12,right: 12,top: 12), + child: Row( + children: [ + Expanded( + child: showProgress( + title: "Requester Info".needTranslation, + status: _currentIndex == 0 + ? "InProgress".needTranslation + : _currentIndex > 0 + ? "Completed".needTranslation + : "Locked".needTranslation, + color: _currentIndex == 0 ? AppColors.infoColor : AppColors.successColor, + ), + ), + Expanded( + child: showProgress( + title:"Patient Info".needTranslation, + status: _currentIndex == 1 + ? "InProgress".needTranslation + : _currentIndex > 1 + ? "Completed".needTranslation + : "Locked".needTranslation, + color: _currentIndex == 1 + ? AppColors.infoColor + : _currentIndex > 1 + ? AppColors.successColor + : AppColors.greyColor, + ), + ), + showProgress( + title: "Other Info".needTranslation, + status: _currentIndex == 2 ? "InProgress".needTranslation :"Locked".needTranslation, + color: _currentIndex == 2 + ? AppColors.infoColor + : _currentIndex > 3 + ? AppColors.successColor + : AppColors.greyColor, + isNeedBorder: false, + ), + ], + ), + ), + Expanded( + child: PageView( + physics: NeverScrollableScrollPhysics(), + controller: _controller, + onPageChanged: (index) { + setState(() { + _currentIndex = index; + }); + }, + scrollDirection: Axis.horizontal, + children: [ + // NewEReferralStepOnePage( + // changePageViewIndex: changePageViewIndex, + // createEReferralRequestModel: createEReferralRequestModel, + // ), + // NewEReferralStepTowPage( + // changePageViewIndex: changePageViewIndex, + // createEReferralRequestModel: createEReferralRequestModel, + // ), + // NewEReferralStepThreePage( + // changePageViewIndex: changePageViewIndex, + // createEReferralRequestModel: createEReferralRequestModel, + // ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget showProgress({required String title, required String status, required Color color, bool isNeedBorder = true}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 26, + height: 26, + // decoration: containerRadius(color, 200), + child: Icon( + Icons.done, + color: Colors.white, + size: 16, + ), + ), + if (isNeedBorder) + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child:Divider(), + )), + ], + ), + // mHeight(8), + Text( + title, + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + letterSpacing: -0.44, + ), + ), + // mHeight(2), + Container( + padding: EdgeInsets.all(5), + // decoration: containerRadius(color.withOpacity(0.2), 4), + child: Text( + status, + style: TextStyle( + fontSize: 8, + fontWeight: FontWeight.w600, + letterSpacing: -0.32, + color: color, + ), + ), + ), + ], + ) + ], + ); + } +} diff --git a/lib/presentation/emergency_services/RRT/rrt_map_screen.dart b/lib/presentation/emergency_services/RRT/rrt_map_screen.dart new file mode 100644 index 00000000..3a17e5d3 --- /dev/null +++ b/lib/presentation/emergency_services/RRT/rrt_map_screen.dart @@ -0,0 +1,633 @@ +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_export.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/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/AmbulanceCallingPlace.dart'; +import 'package:hmg_patient_app_new/features/location/GeocodeResponse.dart'; +import 'package:hmg_patient_app_new/features/location/PlaceDetails.dart'; +import 'package:hmg_patient_app_new/features/location/PlacePrediction.dart'; +import 'package:hmg_patient_app_new/features/location/location_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_doctor_card.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/AddressItem.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/appointment_bottom_sheet.dart' show AppointmentBottomSheet; +import 'package:hmg_patient_app_new/presentation/emergency_services/widgets/location_input_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/CustomSwitch.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/expandable_bottom_sheet/ExpandableBottomSheet.dart'; +import 'package:hmg_patient_app_new/widgets/expandable_bottom_sheet/model/BottomSheetType.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:hmg_patient_app_new/widgets/map/HMSMap.dart'; +import 'package:hmg_patient_app_new/widgets/map/gms_map.dart'; +import 'package:provider/provider.dart'; + +import '../../../widgets/common_bottom_sheet.dart'; + +class RrtMapScreen extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButton: Visibility( + visible: context.watch().bottomSheetType == + BottomSheetType.FIXED, + child: Padding( + padding: EdgeInsetsDirectional.only(end: 8.h, bottom: 68.h), + child: DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, borderRadius: 12.h), + child: Utils.buildSvgWithAssets( + icon: AppAssets.locate_me, width: 24.h, height: 24.h) + .paddingAll(12.h) + .onPress(() { + context + .read() + .moveToCurrentLocation(); + }), + ), + ), + ), + bottomSheet: ExpandableBottomSheet( + bottomSheetType: + context.watch().bottomSheetType, + children: { + BottomSheetType.EXPANDED: ExpanedBottomSheet(context), + BottomSheetType.FIXED: FixedBottomSheet(context), + }, + ), + body: Stack( + children: [ + if (context.read().isGMSAvailable) + GMSMap( + currentLocation: + context.read().getGMSLocation(), + onCameraMoved: (value) => context + .read() + .handleGMSMapCameraMoved(value), + onCameraIdle: + context.read().handleOnCameraIdle, + myLocationEnabled: true, + inputController: + context.read().gmsController, + showCenterMarker: true, + ) + else + HMSMap( + currentLocation: + context.read().getHMSLocation(), + onCameraMoved: (value) => context + .read() + .handleHMSMapCameraMoved(value), + onCameraIdle: + context.read().handleOnCameraIdle, + myLocationEnabled: false, + inputController: + context.read().hmsController, + showCenterMarker: true, + ), + Align( + alignment: AlignmentDirectional.topStart, + child: Utils.buildSvgWithAssets( + icon: AppAssets.closeBottomNav, width: 32.h, height: 32.h) + .onPress(() { + context + .read() + .flushPickupInformation(); + Navigator.pop(context); + }), + ).paddingOnly(top: 51.h, left: 24.h), + ], + ), + ); + } + + Widget FixedBottomSheet(BuildContext context) { + return GestureDetector( + onVerticalDragUpdate: (details){ + }, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + spacing: 24.h, + children: [ + inputFields(context).paddingSymmetrical(16.h, 0.h), + SizedBox( + child: DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.scaffoldBgColor, + customBorder: BorderRadius.only( + topLeft: Radius.circular(24.h), + topRight: Radius.circular(24.h), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 24.h, + children: [ + Column( + spacing: 4.h, + children: [ + "Select Location".needTranslation.toText21( + weight: FontWeight.w600, + color: AppColors.textColor, + ), + "Please select the location".needTranslation.toText12( + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ) + ], + ), + CustomButton( + text: "Submit Request".needTranslation, + onPressed: () { + LocationViewModel locationViewModel = context.read(); + GeocodeResponse? response = locationViewModel.geocodeResponse; + PlaceDetails? placeDetails = locationViewModel.placeDetails; + PlacePrediction? placePrediction = locationViewModel.selectedPrediction; + context.read().submitRRTRequest(response?.results.first, placeDetails, placePrediction); + }, + ) + ], + ).paddingOnly(top: 24.h, bottom: 32.h, left: 24.h, right: 24.h), + ), + ), + ], + ), + ], + ), + ); + } + + Widget ExpanedBottomSheet(BuildContext context) { + return GestureDetector( + onVerticalDragUpdate: (details){ + if(details.delta.dy>0){ + context.read().updateBottomSheetState(BottomSheetType.FIXED); + } + }, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.scaffoldBgColor, + customBorder: BorderRadius.only( + topLeft: Radius.circular(24.h), + topRight: Radius.circular(24.h), + ), + ), + child: Column( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 16.h, + children: [ + + hospitalAndPickUpSection(context), + + + ], + ).paddingOnly(top: 24.h, bottom: 32.h,left: 24.h, right: 24.h), + + bottomPriceContent(context) + ], + ), + ), + ], + ), + ); + } + + locationsSections(BuildContext context) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + ), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + ListView.separated( + separatorBuilder: (_, __) => Column( + children: [ + SizedBox(height: 16.h), + Divider( + color: AppColors.bottomNAVBorder, + height: 1, + thickness: 1, + ), + SizedBox(height: 16.h), + ], + ), + shrinkWrap: true, + itemCount: 3, + itemBuilder: (__, index) { + if (index == + 2) // todo means the end of the list so handle as per the viewmodel + { + return CustomButton( + height: 40.h, + backgroundColor: AppColors.lightRedButtonColor, + borderColor: Colors.transparent, + text: "Add new address".needTranslation, + textColor: AppColors.primaryRedColor, + iconColor: AppColors.primaryRedColor, + onPressed: () {}, + icon: AppAssets.add_icon); + } else { + return AddressItem( + isSelected: index == 0, + address: + "Flat No 301, Building No 12, Palm Spring Apartment, Sector 45, Gurugram, Haryana 122003", + title: index == 0 + ? "Home".needTranslation + : "Work".needTranslation, + onTap: () {}, + ); + } + }) + ]).paddingAll(16.h), + ); + } + + hospitalAndPickUpSection(BuildContext context) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 16.h, + children: [ + // Row( + // children: [ + // hospitalAndPickUpItemContent( + // title: "Select Hospital".needTranslation, + // subTitle: "hospital".needTranslation, + // leadingIcon: AppAssets.hospital, + // ), + // Utils.buildSvgWithAssets(icon: AppAssets.down_cheveron, + // width: 24.h, height: 24.h) + // .paddingAll(16.h) + // ], + // ).onPress((){ + // showHospitalBottomSheet(context); + // }), + // SizedBox(height: 16.h), + // Divider( + // color: AppColors.bottomNAVBorder, + // height: 1, + // thickness: 1, + // ), + // SizedBox(height: 16.h), + + Row( + children: [ + hospitalAndPickUpItemContent( + title: "Pick".needTranslation, + subTitle: "Inside the home".needTranslation, + leadingIcon: AppAssets.pickup_bed, + ), + CustomSwitch( + value: context + .watch() + .pickupFromInsideTheLocation, + onChanged: (value){ + context + .read() + .updateThePickupPlaceFromLocation(value); + }, + ) + ], + ), + + Row( + children: [ + hospitalAndPickUpItemContent( + title: '', + subTitle: "Have any appointment".needTranslation, + leadingIcon: AppAssets.appointment_checkin_icon, + ), + CustomSwitch( + value: context + .watch() + .haveAnAppointment, + onChanged: (value) async { + // if (value) { + // openAppointmentList(context); + // } + await context.read() + .updateAppointment(value); + if (context.read().appointments?.isNotEmpty == true) { + openAppointmentList(context); + } + }, + ) + ], + ), + ], + ).paddingAll(16.h), + ); + } + + leadingIcon(String leadingIcon) { + return Container( + height: 40.h, + width: 40.h, + margin: EdgeInsets.only(right: 10.h), + padding: EdgeInsets.all(8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + borderRadius: 12.h, + color: AppColors.greyColor, + ), + child: Utils.buildSvgWithAssets(icon: leadingIcon), + ); + } + + hospitalAndPickUpItemContent({ + required String title, + required String subTitle, + required String leadingIcon, + }) { + return Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + this.leadingIcon(leadingIcon), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Visibility( + visible: title.isNotEmpty, + child: Column( + children: [ + title.toText12( + color: AppColors.greyTextColor, + fontWeight: FontWeight.w500, + ), + SizedBox(height: 2.h), + ], + ), + ), + subTitle.toText14( + color: AppColors.textColor, + weight: FontWeight.w500, + ), + ], + ), + ), + ], + ), + ); + } + + bottomPriceContent(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.scaffoldBgColor, + customBorder: BorderRadius.only( + topLeft: Radius.circular(24.h), + topRight: Radius.circular(24.h), + ), + hasShadow: true + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12.h, + children: [ + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4.h, + children: [ + "Total amount to pay".needTranslation.toText18( + weight: FontWeight.w600, + color: AppColors.textColor, + ), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.warning, + height: 18.h, width: 18.h), + SizedBox(width: 4.h,), + "Amount will be paid at the hospital" + .needTranslation + .toText12( + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ), + ], + ) + ], + ), + ), + Utils.getPaymentAmountWithSymbol( + (Utils.formatNumberToInternationalFormat(context + .read() + .getTotalPrice() ?? + 0)) + .toText24( + fontWeight: FontWeight.w600, + color: AppColors.textColor, + letterSpacing: -2), + AppColors.blackColor, + 17.h) + + // Utils.getPaymentAmountWithSymbol2(context.read().selectedTransportOption?.priceTotal??"0", letterSpacing: -2) + ], + ), + CustomButton( + text: "Submit Request".needTranslation, + onPressed: () { + LocationViewModel locationViewModel = context.read(); + GeocodeResponse? response = locationViewModel.geocodeResponse; + PlaceDetails? placeDetails = locationViewModel.placeDetails; + PlacePrediction? placePrediction = locationViewModel.selectedPrediction; + context.read().submitAmbulanceRequest(response?.results.first, placeDetails, placePrediction); + }) + ], + ).paddingOnly(top: 24.h, bottom: 12.h, left: 24.h, right: 24.h), + ), + ], + ); + } + + showHospitalBottomSheet(BuildContext context){ + showCommonBottomSheetWithoutHeight( + title: + LocaleKeys.selectHospital.tr(), + context, + child: Consumer( + builder:(_,vm,__)=> HospitalBottomSheetBody( + searchText: vm.searchController, + displayList: vm.displayList, + onFacilityClicked: (value) { + vm.setSelectedFacility(value); + vm.getDisplayList(); + }, + onHospitalClicked: (hospital) { + Navigator.pop(context); + vm.setSelectedHospital(hospital); + }, + onHospitalSearch: (value) { + vm.searchHospitals(value ?? ""); + }, + selectedFacility: + vm.selectedFacility, + hmcCount: vm.hmcCount, + hmgCount: vm.hmgCount, + ), + ), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () { + context.read().clearSearchText(); + }, + ); + } + + ///it will show the places field first and then hospital field + PlaceFirstThanHospitalField(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 16.h, + children: [ + textPlaceInput(context), + hospitalField(context), + ], + ).paddingOnly(right: 24.h, left: 24.h); + } + + HospitalFieldFirstThanPlaces(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 16.h, + children: [hospitalField(context), textPlaceInput(context)], + ).paddingOnly(right: 24.h, left: 24.h); + } + + textPlaceInput(context) { + return Consumer(builder: (_, vm, __) { + print( + "the data is ${vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description}"); + return SizedBox( + width: MediaQuery.sizeOf(context).width, + child: TextInputWidget( + labelText: "Enter Pickup Location Manually".needTranslation, + hintText: "Enter Pickup Location".needTranslation, + controller: TextEditingController( + text: vm.geocodeResponse?.results.first.formattedAddress ?? + vm.selectedPrediction?.description, + ), + leadingIcon: AppAssets.location_pickup, + isAllowLeadingIcon: true, + isEnable: false, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), + ).onPress(() { + openLocationInputBottomSheet(context); + }), + ); + }); + } + + ///decide which field to show first based on the selected calling place + Widget inputFields(BuildContext context) { + return textPlaceInput(context); + } + + openLocationInputBottomSheet(BuildContext context) { + context.read().flushSearchPredictions(); + showCommonBottomSheetWithoutHeight( + title: "".needTranslation, + context, + child: SizedBox( + height: MediaQuery.sizeOf(context).height * .8, + child: LocationInputBottomSheet(), + ), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () {}, + ); + } + + hospitalField(BuildContext context) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, borderRadius: 12.h), + child: Row( + children: [ + hospitalAndPickUpItemContent( + title: "Select Hospital".needTranslation, + subTitle: context + .read() + .getSelectedHospitalName() ?? + "Select Hospital".needTranslation, + leadingIcon: AppAssets.hospital, + ), + Utils.buildSvgWithAssets( + icon: AppAssets.down_cheveron, width: 24.h, height: 24.h) + .paddingAll(16.h) + ], + ).onPress(() { + print("the item is clicked"); + showHospitalBottomSheet(context); + }).paddingSymmetrical( + 10.w, + 12.h, + ), + ); + } + + void openAppointmentList(BuildContext context) { + showCommonBottomSheetWithoutHeight( + title: "Select Appointment".needTranslation, + context, + child: SizedBox( + height: MediaQuery.sizeOf(context).height * .5, + child: AppointmentBottomSheet( + list: context.read().appointments!, + onAppointmentSelection: (appointment) { + Navigator.pop(context); + context.read().setSelectedAppointment(appointment); + }), + ), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () { + context.read().checkHasAppointment(); + }, + ); + // ); + } +} diff --git a/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart b/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart index 4d226102..d628c1db 100644 --- a/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart +++ b/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart @@ -1,9 +1,17 @@ +import 'package:easy_localization/easy_localization.dart' show tr, StringTranslateExtension; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.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/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/rrt_procedures_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.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/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/radio/custom_radio_button.dart'; import 'package:provider/provider.dart'; @@ -13,34 +21,259 @@ class RrtRequestTypeSelect extends StatelessWidget { @override Widget build(BuildContext context) { return Consumer(builder: (context, emergencyServicesVM, child) { + print("the checkbox is ${emergencyServicesVM.agreedToTermsAndCondition}"); return Column( children: [ - Container( - padding: EdgeInsets.all(16.h), - height: 200.h, + Column( + children: [ + "Rapid Response Team (RRT) options".needTranslation.toText20(color: AppColors.textColor, isBold: true), + SizedBox( + height: 16.h, + ), + DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: Colors.white, + borderRadius: 20.r, + ), + child: OptionSelection(context)), + SizedBox( + height: 24.h, + ), + termsAndCondition(context, emergencyServicesVM), + ], + ).paddingSymmetrical(24.w, 0.h), + bottomPriceContent(context, emergencyServicesVM) + ], + ); + }); + } + + bottomPriceContent(BuildContext context, EmergencyServicesViewModel emergencyServicesVM) { + if (emergencyServicesVM.selectedRRTProcedure == null) return SizedBox.shrink(); + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + DecoratedBox( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - ), + color: AppColors.scaffoldBgColor, + customBorder: BorderRadius.only( + topLeft: Radius.circular(24.h), + topRight: Radius.circular(24.h), + ), + hasShadow: true), child: Column( crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12.h, children: [ Row( + mainAxisAlignment: MainAxisAlignment.end, children: [ - CustomRadioOption( - value: "", - groupValue: "", - onChanged: (value) {}, - text: "Home Visit Emergency", + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12.h, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Total amount to pay".needTranslation.toText18( + weight: FontWeight.w600, + color: AppColors.textColor, + ), + Utils.getPaymentAmountWithSymbol( + (Utils.formatNumberToInternationalFormat(context.read().selectedRRTProcedure?.patientShare ?? 0)) + .toText24(fontWeight: FontWeight.w600, color: AppColors.textColor, letterSpacing: -2), + AppColors.blackColor, + 17.h) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.warning, height: 18.h, width: 18.h), + SizedBox( + width: 4.h, + ), + "Amount will be paid at the hospital".needTranslation.toText11( + color: AppColors.greyTextColor, + ), + ], + ), + Row( + children: [ + "+ VAT 15%(".needTranslation.toText12( + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ), + "${emergencyServicesVM.selectedRRTProcedure?.patientTaxAmount})".needTranslation.toText14( + weight: FontWeight.w600, + color: AppColors.greyTextColor, + ), + ], + ), + ], + ) + ], + ), + ), + + // Utils.getPaymentAmountWithSymbol2(context.read().selectedTransportOption?.priceTotal??"0", letterSpacing: -2) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + spacing: 6.w, + children: [ + Image.asset(AppAssets.mada, width: 24.h, height: 24.h), + Image.asset(AppAssets.visa, width: 24.h, height: 24.h), + Image.asset(AppAssets.Mastercard, width: 24.h, height: 24.h), + Image.asset(AppAssets.apple_pay, width: 24.h, height: 24.h), + ], + ), + Column( + children: [ + Divider( + color: AppColors.dividerColor, + thickness: 1.h, + ), + Utils.getPaymentAmountWithSymbol( + (Utils.formatNumberToInternationalFormat(context.read().selectedRRTProcedure?.patientShareWithTax ?? 0)) + .toText24(fontWeight: FontWeight.w600, color: AppColors.textColor, letterSpacing: -2), + AppColors.blackColor, + 17.h), + ], ) ], ), + CustomButton(text: LocaleKeys.next.tr(), onPressed: () { + Navigator.pop(context); + emergencyServicesVM.openRRT(); + }) ], - ), + ).paddingAll(24.h), ), - SizedBox(height: 32.h), ], ); - }); + } + + Widget OptionSelection(BuildContext context) { + return Selector?, RRTProceduresResponseModel?)>( + // Select both the list and the currently selected procedure + selector: (context, viewModel) => (viewModel.RRTProceduresList, viewModel.selectedRRTProcedure), + builder: (context, data, child) { + final procedureList = data.$1; + final selectedProcedure = data.$2; + + // Handle loading or empty state + if (procedureList == null || procedureList.isEmpty) { + return const SizedBox.shrink(); + } + + return ListView.separated( + shrinkWrap: true, + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 16.w), + // Important for ListView inside a Column + physics: const NeverScrollableScrollPhysics(), + // If the parent is scrollable + itemCount: procedureList.length, + separatorBuilder: (_, __) => Divider(thickness: 1.h, color: AppColors.dividerColor), + itemBuilder: (_, index) { + final procedure = procedureList[index]; + + final indexOfSelectedItem = selectedProcedure != null ? procedureList.indexOf(selectedProcedure) : -1; + return RadioListTile( + title: (procedure.procedureName ?? "").toText16(color: AppColors.textColor, weight: FontWeight.w500), + value: index, + fillColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return AppColors.primaryRedColor; + } + return Color(0xffEEEEEE); + }), + contentPadding: EdgeInsets.zero, + groupValue: indexOfSelectedItem, + onChanged: (int? newValue) { + context.read().setSelectedRRTProcedure(procedure); + }, + ); + // return Row( + // children: [ + // Radio( + // // Specify the type for the Radio button + // value: procedure.procedureID ?? "", + // groupValue: selectedProcedure?.procedureID, + // // Compare with the selected procedure's ID + // activeColor: AppColors.primaryRedColor, + // fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), + // onChanged: (value) { + // // The onChanged of the Radio button should handle the state update + // context.read().setSelectedRRTProcedure(procedure); + // }, + // ), + // Expanded( + // // Use Expanded to allow text to wrap if it's too long + // child: (procedure.procedureName ?? "").toText12(color: AppColors.textColor), + // ) + // ], + }, + ); + }, + ); + } + + termsAndCondition( + BuildContext context, + EmergencyServicesViewModel emergencyServicesVM, + ) { + return Row( + children: [ + SizedBox( + height: 18.h, + width: 18.w, + child: Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: emergencyServicesVM.agreedToTermsAndCondition, + checkColor: AppColors.whiteColor, + fillColor: MaterialStateProperty.resolveWith((Set states) { + print("the state is ${states}"); + if (states.contains(WidgetState.selected)) { + return AppColors.errorColor; + } + return AppColors.whiteColor; + }), + onChanged: (value) { + emergencyServicesVM.setTermsAndConditions(value ?? false); + }), + ), + Row( + spacing: 4.w, + children: [ + SizedBox.shrink(), + LocaleKeys.agreeTo.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), + LocaleKeys.termsConditoins.tr().toText16(color: AppColors.errorColor, isUnderLine: true, weight: FontWeight.w500).onPress(() { + emergencyServicesVM.getTermsAndConditions(); + }), + ], + ), + ], + ); + + // CheckboxListTile( + // title: Row( + // children: [ + // LocaleKeys.agreeTo.tr().toText12(color: AppColors.textColor), + // LocaleKeys.termsConditoins.tr().toText12(color: AppColors.errorColor, isUnderLine: true), + // ], + // ), + // value: emergencyServicesVM.agreedToTermsAndCondition, + // onChanged: (value) { + // emergencyServicesVM.setTermsAndConditions(value ?? false); + // }, + // ); } } diff --git a/lib/presentation/emergency_services/RRT/terms_and_condition.dart b/lib/presentation/emergency_services/RRT/terms_and_condition.dart new file mode 100644 index 00000000..8a507922 --- /dev/null +++ b/lib/presentation/emergency_services/RRT/terms_and_condition.dart @@ -0,0 +1,33 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_widget_from_html/flutter_widget_from_html.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/emergency_services/emergency_services_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:provider/provider.dart'; + +class TermsAndCondition extends StatelessWidget { + final String termsAndCondition; + const TermsAndCondition({super.key, required this.termsAndCondition}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + + Expanded( + child: CollapsingListView( + title: "Terms And Condition".needTranslation, + child:DecoratedBox(decoration:RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ),child: HtmlWidget(termsAndCondition).paddingAll(16.h)).paddingAll(12.h)))])); + } + +} \ No newline at end of file diff --git a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart index 5e4b8856..043c2316 100644 --- a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart +++ b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart @@ -1,5 +1,3 @@ -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'; @@ -14,10 +12,7 @@ import 'package:hmg_patient_app_new/features/location/PlaceDetails.dart'; import 'package:hmg_patient_app_new/features/location/PlacePrediction.dart'; import 'package:hmg_patient_app_new/features/location/location_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_doctor_card.dart'; -import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/requesting_services_page.dart' show RequestingServicesPage; -import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/tracking_screen.dart' show TrackingScreen; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/appointment_bottom_sheet.dart' show AppointmentBottomSheet; import 'package:hmg_patient_app_new/presentation/emergency_services/widgets/location_input_bottom_sheet.dart'; @@ -28,8 +23,7 @@ import 'package:hmg_patient_app_new/widgets/expandable_bottom_sheet/ExpandableBo import 'package:hmg_patient_app_new/widgets/expandable_bottom_sheet/model/BottomSheetType.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:hmg_patient_app_new/widgets/map/HMSMap.dart'; -import 'package:hmg_patient_app_new/widgets/map/map.dart'; -import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:hmg_patient_app_new/widgets/map/gms_map.dart'; import 'package:provider/provider.dart'; import '../../../widgets/common_bottom_sheet.dart'; @@ -40,20 +34,13 @@ class CallAmbulancePage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( floatingActionButton: Visibility( - visible: context.watch().bottomSheetType == - BottomSheetType.FIXED, + visible: context.watch().bottomSheetType == BottomSheetType.FIXED, child: Padding( padding: EdgeInsetsDirectional.only(end: 8.h, bottom: 68.h), child: DecoratedBox( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, borderRadius: 12.h), - child: Utils.buildSvgWithAssets( - icon: AppAssets.locate_me, width: 24.h, height: 24.h) - .paddingAll(12.h) - .onPress(() { - context - .read() - .moveToCurrentLocation(); + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), + child: Utils.buildSvgWithAssets(icon: AppAssets.locate_me, width: 24.h, height: 24.h).paddingAll(12.h).onPress(() { + context.read().moveToCurrentLocation(); }), ), ), @@ -62,8 +49,7 @@ class CallAmbulancePage extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ ExpandableBottomSheet( - bottomSheetType: - context.watch().bottomSheetType, + bottomSheetType: context.watch().bottomSheetType, children: { BottomSheetType.EXPANDED: ExpanedBottomSheet(context), BottomSheetType.FIXED: FixedBottomSheet(context), @@ -73,42 +59,28 @@ class CallAmbulancePage extends StatelessWidget { ), body: Stack( children: [ - if (context.read().isGMSAvailable ) + if (context.read().isGMSAvailable) GMSMap( - currentLocation: - context.read().getGMSLocation(), - onCameraMoved: (value) => context - .read() - .handleGMSMapCameraMoved(value), - onCameraIdle: - context.read().handleOnCameraIdle, + currentLocation: context.read().getGMSLocation(), + onCameraMoved: (value) => context.read().handleGMSMapCameraMoved(value), + onCameraIdle: context.read().handleOnCameraIdle, myLocationEnabled: true, - inputController: - context.read().gmsController, + inputController: context.read().gmsController, showCenterMarker: true, ) else HMSMap( - currentLocation: - context.read().getHMSLocation(), - onCameraMoved: (value) => context - .read() - .handleHMSMapCameraMoved(value), - onCameraIdle: - context.read().handleOnCameraIdle, + currentLocation: context.read().getHMSLocation(), + onCameraMoved: (value) => context.read().handleHMSMapCameraMoved(value), + onCameraIdle: context.read().handleOnCameraIdle, myLocationEnabled: false, - inputController: - context.read().hmsController, + inputController: context.read().hmsController, showCenterMarker: true, ), Align( alignment: AlignmentDirectional.topStart, - child: Utils.buildSvgWithAssets( - icon: AppAssets.closeBottomNav, width: 32.h, height: 32.h) - .onPress(() { - context - .read() - .flushPickupInformation(); + child: Utils.buildSvgWithAssets(icon: AppAssets.closeBottomNav, width: 32.h, height: 32.h).onPress(() { + context.read().flushPickupInformation(); Navigator.pop(context); }), ).paddingOnly(top: 51.h, left: 24.h), @@ -119,8 +91,7 @@ class CallAmbulancePage extends StatelessWidget { Widget FixedBottomSheet(BuildContext context) { return GestureDetector( - onVerticalDragUpdate: (details){ - + onVerticalDragUpdate: (details) { // if(details.delta.dy<0){ // // context.read().updateBottomSheetState(BottomSheetType.EXPANDED); @@ -161,7 +132,7 @@ class CallAmbulancePage extends StatelessWidget { // ), // ), // ), - // .paddingOnly(right: 24.h, bottom: 24.h), + // .paddingOnly(right: 24.h, bottom: 24.h), Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, @@ -189,9 +160,7 @@ class CallAmbulancePage extends StatelessWidget { weight: FontWeight.w600, color: AppColors.textColor, ), - " Please select the details of pickup" - .needTranslation - .toText12( + " Please select the details of pickup".needTranslation.toText12( fontWeight: FontWeight.w500, color: AppColors.greyTextColor, ) @@ -200,9 +169,7 @@ class CallAmbulancePage extends StatelessWidget { CustomButton( text: "Select Details".needTranslation, onPressed: () { - context - .read() - .updateBottomSheetState(BottomSheetType.EXPANDED); + context.read().updateBottomSheetState(BottomSheetType.EXPANDED); }) ], ).paddingOnly(top: 24.h, bottom: 32.h, left: 24.h, right: 24.h), @@ -217,11 +184,11 @@ class CallAmbulancePage extends StatelessWidget { Widget ExpanedBottomSheet(BuildContext context) { return GestureDetector( - onVerticalDragUpdate: (details){ - if(details.delta.dy>0){ - context.read().updateBottomSheetState(BottomSheetType.FIXED); - } - }, + onVerticalDragUpdate: (details) { + if (details.delta.dy > 0) { + context.read().updateBottomSheetState(BottomSheetType.FIXED); + } + }, child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -239,22 +206,19 @@ class CallAmbulancePage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 16.h, children: [ - hospitalAndPickUpSection(context), - if(context.read().appointment != null) - AppointmentDoctorCard( - renderWidgetForERDisplay: true, - patientAppointmentHistoryResponseModel: context.read().appointment!, - onAskDoctorTap: () {}, - onCancelTap: () async {}, - onRescheduleTap: () async {}, - ).onPress((){ - openAppointmentList(context); - }) - + if (context.read().appointment != null) + AppointmentDoctorCard( + renderWidgetForERDisplay: true, + patientAppointmentHistoryResponseModel: context.read().appointment!, + onAskDoctorTap: () {}, + onCancelTap: () async {}, + onRescheduleTap: () async {}, + ).onPress(() { + openAppointmentList(context); + }) ], - ).paddingOnly(top: 24.h, bottom: 32.h,left: 24.h, right: 24.h), - + ).paddingOnly(top: 24.h, bottom: 32.h, left: 24.h, right: 24.h), bottomPriceContent(context) ], ), @@ -286,8 +250,7 @@ class CallAmbulancePage extends StatelessWidget { shrinkWrap: true, itemCount: 3, itemBuilder: (__, index) { - if (index == - 2) // todo means the end of the list so handle as per the viewmodel + if (index == 2) // todo means the end of the list so handle as per the viewmodel { return CustomButton( height: 40.h, @@ -301,11 +264,8 @@ class CallAmbulancePage extends StatelessWidget { } else { return AddressItem( isSelected: index == 0, - address: - "Flat No 301, Building No 12, Palm Spring Apartment, Sector 45, Gurugram, Haryana 122003", - title: index == 0 - ? "Home".needTranslation - : "Work".needTranslation, + address: "Flat No 301, Building No 12, Palm Spring Apartment, Sector 45, Gurugram, Haryana 122003", + title: index == 0 ? "Home".needTranslation : "Work".needTranslation, onTap: () {}, ); } @@ -354,13 +314,9 @@ class CallAmbulancePage extends StatelessWidget { leadingIcon: AppAssets.pickup_bed, ), CustomSwitch( - value: context - .watch() - .pickupFromInsideTheLocation, - onChanged: (value){ - context - .read() - .updateThePickupPlaceFromLocation(value); + value: context.watch().pickupFromInsideTheLocation, + onChanged: (value) { + context.read().updateThePickupPlaceFromLocation(value); }, ) ], @@ -369,20 +325,17 @@ class CallAmbulancePage extends StatelessWidget { Row( children: [ hospitalAndPickUpItemContent( - title: '', + title: 'Appointment', subTitle: "Have any appointment".needTranslation, - leadingIcon: AppAssets.appointment_checkin_icon, + leadingIcon: AppAssets.appointment_calendar_icon, ), CustomSwitch( - value: context - .watch() - .haveAnAppointment, + value: context.watch().haveAnAppointment, onChanged: (value) async { // if (value) { // openAppointmentList(context); // } - await context.read() - .updateAppointment(value); + await context.read().updateAppointment(value); if (context.read().appointments?.isNotEmpty == true) { openAppointmentList(context); } @@ -405,7 +358,7 @@ class CallAmbulancePage extends StatelessWidget { borderRadius: 12.h, color: AppColors.greyColor, ), - child: Utils.buildSvgWithAssets(icon: leadingIcon), + child: Utils.buildSvgWithAssets(icon: leadingIcon, iconColor: AppColors.greyTextColor), ); } @@ -454,55 +407,47 @@ class CallAmbulancePage extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ - DecoratedBox( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.scaffoldBgColor, - customBorder: BorderRadius.only( - topLeft: Radius.circular(24.h), - topRight: Radius.circular(24.h), - ), - hasShadow: true + DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.scaffoldBgColor, + customBorder: BorderRadius.only( + topLeft: Radius.circular(24.h), + topRight: Radius.circular(24.h), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 12.h, + hasShadow: true), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12.h, + children: [ + Row( children: [ - Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 4.h, - children: [ - "Total amount to pay".needTranslation.toText18( + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4.h, + children: [ + "Total amount to pay".needTranslation.toText18( weight: FontWeight.w600, color: AppColors.textColor, ), - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.warning, - height: 18.h, width: 18.h), - SizedBox(width: 4.h,), - "Amount will be paid at the hospital" - .needTranslation - .toText12( + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.warning, height: 18.h, width: 18.h), + SizedBox( + width: 4.h, + ), + "Amount will be paid at the hospital".needTranslation.toText12( fontWeight: FontWeight.w500, color: AppColors.greyTextColor, ), - ], - ) ], - ), - ), + ) + ], + ), + ), Utils.getPaymentAmountWithSymbol( - (Utils.formatNumberToInternationalFormat(context - .read() - .getTotalPrice() ?? - 0)) - .toText24( - fontWeight: FontWeight.w600, - color: AppColors.textColor, - letterSpacing: -2), + (Utils.formatNumberToInternationalFormat(context.read().getTotalPrice() ?? 0)) + .toText24(fontWeight: FontWeight.w600, color: AppColors.textColor, letterSpacing: -2), AppColors.blackColor, 17.h) @@ -518,20 +463,19 @@ class CallAmbulancePage extends StatelessWidget { PlacePrediction? placePrediction = locationViewModel.selectedPrediction; context.read().submitAmbulanceRequest(response?.results.first, placeDetails, placePrediction); }) - ], - ).paddingOnly(top: 24.h, bottom: 12.h, left: 24.h, right: 24.h), - ), + ], + ).paddingOnly(top: 24.h, bottom: 12.h, left: 24.h, right: 24.h), + ), ], ); } - showHospitalBottomSheet(BuildContext context){ + showHospitalBottomSheet(BuildContext context) { showCommonBottomSheetWithoutHeight( - title: - LocaleKeys.selectHospital.tr(), + title: LocaleKeys.selectHospital.tr(), context, child: Consumer( - builder:(_,vm,__)=> HospitalBottomSheetBody( + builder: (_, vm, __) => HospitalBottomSheetBody( searchText: vm.searchController, displayList: vm.displayList, onFacilityClicked: (value) { @@ -545,8 +489,7 @@ class CallAmbulancePage extends StatelessWidget { onHospitalSearch: (value) { vm.searchHospitals(value ?? ""); }, - selectedFacility: - vm.selectedFacility, + selectedFacility: vm.selectedFacility, hmcCount: vm.hmcCount, hmgCount: vm.hmgCount, ), @@ -583,16 +526,14 @@ class CallAmbulancePage extends StatelessWidget { textPlaceInput(context) { return Consumer(builder: (_, vm, __) { - print( - "the data is ${vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description}"); + print("the data is ${vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description}"); return SizedBox( width: MediaQuery.sizeOf(context).width, child: TextInputWidget( labelText: "Enter Pickup Location Manually".needTranslation, hintText: "Enter Pickup Location".needTranslation, controller: TextEditingController( - text: vm.geocodeResponse?.results.first.formattedAddress ?? - vm.selectedPrediction?.description, + text: vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description, ), leadingIcon: AppAssets.location_pickup, isAllowLeadingIcon: true, @@ -614,8 +555,7 @@ class CallAmbulancePage extends StatelessWidget { ///decide which field to show first based on the selected calling place inputFields(BuildContext context) { - return context.read().callingPlace == - AmbulanceCallingPlace.FROM_HOSPITAL + return context.read().callingPlace == AmbulanceCallingPlace.FROM_HOSPITAL ? HospitalFieldFirstThanPlaces(context) : PlaceFirstThanHospitalField(context); } @@ -639,21 +579,15 @@ class CallAmbulancePage extends StatelessWidget { hospitalField(BuildContext context) { return DecoratedBox( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, borderRadius: 12.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), child: Row( children: [ hospitalAndPickUpItemContent( title: "Select Hospital".needTranslation, - subTitle: context - .read() - .getSelectedHospitalName() ?? - "Select Hospital".needTranslation, + subTitle: context.read().getSelectedHospitalName() ?? "Select Hospital".needTranslation, leadingIcon: AppAssets.hospital, ), - Utils.buildSvgWithAssets( - icon: AppAssets.down_cheveron, width: 24.h, height: 24.h) - .paddingAll(16.h) + Utils.buildSvgWithAssets(icon: AppAssets.down_cheveron, width: 24.h, height: 24.h).paddingAll(16.h) ], ).onPress(() { print("the item is clicked"); diff --git a/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart b/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart index eddca119..e5dc0b95 100644 --- a/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart +++ b/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart @@ -8,12 +8,13 @@ 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/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/AmbulanceRequestOrdersModel.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/RRTServiceData.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/map/HMSMap.dart'; -import 'package:hmg_patient_app_new/widgets/map/map.dart' show GMSMap; +import 'package:hmg_patient_app_new/widgets/map/gms_map.dart' show GMSMap; import 'package:hmg_patient_app_new/widgets/order_tracking/order_tracking_state.dart'; import 'package:hmg_patient_app_new/widgets/order_tracking/request_tracking.dart'; import 'package:lottie/lottie.dart'; @@ -22,11 +23,13 @@ import 'package:url_launcher/url_launcher.dart' show launchUrl; class TrackingScreen extends StatelessWidget { OrderTrackingState? state ; + final bool isRRTOrder; final AmbulanceRequestOrdersModel? order; + final GetCMCAllOrdersResponseModel? rrtOrder; - TrackingScreen({super.key, OrderTrackingState? state, this.order}){ + TrackingScreen({super.key, OrderTrackingState? state, this.order, this.isRRTOrder = false, this.rrtOrder}){ if(state == null){ - switch (order?.statusId) { + switch (order?.statusId ?? rrtOrder?.statusId) { case 1: //pending case 2: //processing this.state = OrderTrackingState.waitingForCall; @@ -53,7 +56,8 @@ class TrackingScreen extends StatelessWidget { visible: state == OrderTrackingState.ended, child: SafeArea( child: CustomButton( - height: 56.h, + height: 40.h, + iconSize: 18.w, backgroundColor: AppColors.bgGreenColor, borderColor: Colors.transparent, text: "Close".needTranslation, @@ -70,7 +74,7 @@ class TrackingScreen extends StatelessWidget { animationSection(), Column( spacing: 16.h, - children: [orderStatus(context), orderTrackingId(), contactSection()], + children: [orderStatus(context), orderTrackingId(context), contactSection()], ).paddingAll(16.h), ], ))), @@ -140,6 +144,8 @@ class TrackingScreen extends StatelessWidget { borderColor: AppColors.primaryRedColor, textColor: Colors.white, icon: AppAssets.cancel, + height: 40.h, + iconSize: 18.w, ), ], ); @@ -153,6 +159,7 @@ class TrackingScreen extends StatelessWidget { mapSection(context), CustomButton( height: 40.h, + iconSize: 18.w, backgroundColor: AppColors.lightRedButtonColor, borderColor: Colors.transparent, text: "Share Your Live Location on Whatsapp".needTranslation, @@ -222,13 +229,13 @@ class TrackingScreen extends StatelessWidget { width: 36.h, child: CustomButton( text: '', - iconSize: 16.h, + iconSize: 18.h, icon: AppAssets.call_fill, onPressed: () {}, backgroundColor: AppColors.lightRedButtonColor, iconColor: AppColors.primaryRedColor, borderColor: Colors.transparent, - height: 36.h, + height: 40.h, ), ) ], @@ -328,11 +335,12 @@ class TrackingScreen extends StatelessWidget { } } - orderTrackingId() { + orderTrackingId(BuildContext context) { if(state == OrderTrackingState.failed){ return SizedBox.shrink(); } return Container( + width: MediaQuery.sizeOf(context).width-32.h, padding: EdgeInsets.all(16.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, @@ -344,20 +352,10 @@ class TrackingScreen extends StatelessWidget { spacing: 8.h, children: [ - Visibility(visible:order != null ,child: "Req ID: ${order?.iD}".toText16(color: AppColors.textColor, weight: FontWeight.w600)), - Row( - spacing: 8.h, - children: [ - Flexible(child: - chip(order?.pickupLocation??"", AppAssets.location_pickup, AppColors.blackBgColor), + Visibility(visible:(order != null || rrtOrder != null) ,child: "Req ID: ${order?.iD?? rrtOrder?.iD}".toText16(color: AppColors.textColor, weight: FontWeight.w600)), + if(order != null) ambulanceOrderData() + // else rrtOrderData() - ), - Flexible(child: - chip(order?.dropOffLocation??"", AppAssets.hospital, AppColors.blackBgColor), - - ) - ], - ), ], ), ); @@ -373,6 +371,7 @@ class TrackingScreen extends StatelessWidget { } contactSection() { + if(isRRTOrder) return SizedBox.shrink(); return Container( padding: EdgeInsets.all(16.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -387,8 +386,12 @@ class TrackingScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 4.h, children: [ - "Contact Rapid Response Team (RRT)".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w600), - "0115259555".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + "Contact".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w600), + "0115259555".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500).onPress((){ + launchUrl( + Uri.parse("tel://0115259555"), + ); + }), SizedBox(height: 8.h), ], ), @@ -456,6 +459,44 @@ class TrackingScreen extends StatelessWidget { openCancelOrderBottomSheet(BuildContext context){ - context.read().cancelOrder(order, shouldPop: true); + if(isRRTOrder){ + context.read().cancelRRTOrder(rrtOrder?.iD??-1, shouldPop: true); + return; + }else { + context.read().cancelOrder(order, shouldPop: true); + return; + } + } + + Widget ambulanceOrderData() { + return Row( + spacing: 8.h, + children: [ + Flexible(child: + chip(order?.pickupLocation??"", AppAssets.location_pickup, AppColors.blackBgColor), + + ), + Flexible(child: + chip(order?.dropOffLocation??"", AppAssets.hospital, AppColors.blackBgColor), + + ) + ], + ); } + + // Widget rrtOrderData() { + // return Row( + // spacing: 8.h, + // children: [ + // Flexible(child: + // chip(rrtOrder?.pickupLocation??"", AppAssets.location_pickup, AppColors.blackBgColor), + // + // ), + // // Flexible(child: + // // chip(rrtOrder?.??"", AppAssets.hospital, AppColors.blackBgColor), + // // + // // ) + // ], + // ); + // } } diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart b/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart index 5d073d4b..9410af1b 100644 --- a/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart +++ b/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart @@ -29,7 +29,7 @@ class PickupLocation extends StatelessWidget { ), "Select Direction" .needTranslation - .toText14(color: AppColors.textColor, weight: FontWeight.w600), + .toText16(color: AppColors.textColor, weight: FontWeight.w600), SizedBox( height: 12.h, ), @@ -61,7 +61,7 @@ class PickupLocation extends StatelessWidget { ), "To Hospital" .needTranslation - .toText12(color: AppColors.textColor) + .toText14(color: AppColors.textColor, weight: FontWeight.w500) ], ).onPress((){ context @@ -78,7 +78,7 @@ class PickupLocation extends StatelessWidget { ), "From Hospital" .needTranslation - .toText12(color: AppColors.textColor) + .toText14(color: AppColors.textColor, weight: FontWeight.w500) ], ).onPress((){ context @@ -96,10 +96,11 @@ class PickupLocation extends StatelessWidget { builder: (context, directionValue, _) { return Column( spacing: 12.h, + crossAxisAlignment: CrossAxisAlignment.start, children: [ "Select Way" .needTranslation - .toText14(color: AppColors.textColor, weight: FontWeight.w600), + .toText16(color: AppColors.textColor, weight: FontWeight.w600), RadioGroup( groupValue: directionValue, onChanged: (value) { @@ -121,7 +122,7 @@ class PickupLocation extends StatelessWidget { ), "One Way" .needTranslation - .toText12(color: AppColors.textColor) + .toText12(color: AppColors.textColor, fontWeight: FontWeight.w500) ], ).onPress((){ context @@ -138,7 +139,7 @@ class PickupLocation extends StatelessWidget { ), "Two Way" .needTranslation - .toText12(color: AppColors.textColor) + .toText14(color: AppColors.textColor, weight: FontWeight.w500) ], ).onPress((){ context diff --git a/lib/presentation/emergency_services/emergency_services_page.dart b/lib/presentation/emergency_services/emergency_services_page.dart index 3cbce230..bce7dafd 100644 --- a/lib/presentation/emergency_services/emergency_services_page.dart +++ b/lib/presentation/emergency_services/emergency_services_page.dart @@ -1,21 +1,18 @@ 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/dependencies.dart'; -import 'package:hmg_patient_app_new/core/location_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/OrderDisplay.dart'; import 'package:hmg_patient_app_new/features/location/location_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/RRT/rrt_request_type_select.dart'; -import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/call_ambulance_page.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/ambulance_option_selection_bottomsheet.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/history/er_history_listing.dart'; -import 'package:hmg_patient_app_new/presentation/emergency_services/nearest_er_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -29,16 +26,17 @@ class EmergencyServicesPage extends StatelessWidget { EmergencyServicesPage({super.key}); late EmergencyServicesViewModel emergencyServicesViewModel; - LocationUtils? locationUtils; + + _handleConfirmationBottomSheet() {} @override Widget build(BuildContext context) { emergencyServicesViewModel = Provider.of(context, listen: false); - locationUtils = getIt.get(); - locationUtils!.isShowConfirmDialog = true; + return CollapsingListView( title: LocaleKeys.emergencyServices.tr(), requests: () { + emergencyServicesViewModel.changeOrderDisplayItems(OrderDislpay.ALL); Navigator.of(context).push(CustomPageRoute(page: ErHistoryListing(), direction: AxisDirection.up)); }, child: Padding( @@ -62,7 +60,9 @@ class EmergencyServicesPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ "Call Ambulance".needTranslation.toText16(isBold: true, color: AppColors.blackColor), - "Request an ambulance in emergency from home or hospital".needTranslation.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + "Request an ambulance in emergency from home or hospital" + .needTranslation + .toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), ], ), ), @@ -70,13 +70,10 @@ class EmergencyServicesPage extends StatelessWidget { Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, width: 13.h, height: 13.h), ], ).onPress(() { - - showCommonBottomSheetWithoutHeight( context, child: Container( - decoration: - RoundedRectangleBorder().toSmoothCornerDecoration( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.primaryRedColor, borderRadius: 24.h, ), @@ -101,29 +98,20 @@ class EmergencyServicesPage extends StatelessWidget { ], ), Lottie.asset(AppAnimations.ambulance_alert, - repeat: false, - reverse: false, - frameRate: FrameRate(60), - width: 120.h, - height: 120.h, - fit: BoxFit.contain), + repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain), SizedBox(height: 8.h), - "Confirmation".needTranslation.toText28( - color: AppColors.whiteColor, isBold: true), + "Confirmation".needTranslation.toText28(color: AppColors.whiteColor, isBold: true), SizedBox(height: 8.h), "Are you sure you want to call an ambulance?" .needTranslation - .toText14( - color: AppColors.whiteColor, - weight: FontWeight.w500), + .toText14(color: AppColors.whiteColor, weight: FontWeight.w500), SizedBox(height: 24.h), CustomButton( text: LocaleKeys.confirm.tr(context: context), onPressed: () async { - // Navigator.of(context).pop(); await emergencyServicesViewModel.getTransportationOption(); - openTranportationSelectionBottomSheet(context); + openTranportationSelectionBottomSheet(context); }, backgroundColor: AppColors.whiteColor, borderColor: AppColors.whiteColor, @@ -161,7 +149,9 @@ class EmergencyServicesPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ "Nearest ER Location".needTranslation.toText16(isBold: true, color: AppColors.blackColor), - "Get the details of nearest branch including directions".needTranslation.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + "Get the details of nearest branch including directions" + .needTranslation + .toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), ], ), ), @@ -189,7 +179,8 @@ class EmergencyServicesPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ "Rapid Response Team (RRT)".toText16(isBold: true, color: AppColors.blackColor), - "Comprehensive medical service for all sorts of urgent and stable cases".toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + "Comprehensive medical service for all sorts of urgent and stable cases" + .toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), ], ), ), @@ -209,26 +200,13 @@ class EmergencyServicesPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "".toText14(), - Utils.buildSvgWithAssets( - icon: AppAssets.cancel_circle_icon, - iconColor: AppColors.whiteColor, - width: 24.h, - height: 24.h, - fit: BoxFit.contain, - ).onPress(() { - Navigator.of(context).pop(); - }), - ], - ), Lottie.asset(AppAnimations.ambulance_alert, repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain), SizedBox(height: 8.h), LocaleKeys.confirm.tr().toText28(color: AppColors.whiteColor, isBold: true), SizedBox(height: 8.h), - "Are you sure you want to call Rapid Response Team (RRT)?".needTranslation.toText14(color: AppColors.whiteColor, weight: FontWeight.w500), + "Are you sure you want to call Rapid Response Team (RRT)?" + .needTranslation + .toText14(color: AppColors.whiteColor, weight: FontWeight.w500), SizedBox(height: 24.h), CustomButton( text: LocaleKeys.confirm.tr(context: context), @@ -236,10 +214,23 @@ class EmergencyServicesPage extends StatelessWidget { Navigator.of(context).pop(); LoaderBottomSheet.showLoader(); + emergencyServicesViewModel.clearRRTData(); await emergencyServicesViewModel.getRRTProcedures(onSuccess: (val) { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( - title: "Rapid Response Team (RRT)".needTranslation, + padding: EdgeInsets.only(top: 24.h), + titleWidget: Transform.flip( + flipX: emergencyServicesViewModel.isArabic, + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrow_back, + iconColor: Color(0xff2B353E), + fit: BoxFit.contain, + ), + ).onPress(() { + + Navigator.pop(context); + }), + // title: "Rapid Response Team (RRT)".needTranslation, context, child: RrtRequestTypeSelect(), isFullScreen: false, @@ -265,7 +256,9 @@ class EmergencyServicesPage extends StatelessWidget { isCloseButtonVisible: false, hasBottomPadding: false, backgroundColor: AppColors.primaryRedColor, - callBackFunc: () {}, + callBackFunc: () { + context.read().setTermsAndConditions(false); + }, ); }), ), @@ -286,7 +279,9 @@ class EmergencyServicesPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ "Emergency Check-In".needTranslation.toText16(isBold: true, color: AppColors.blackColor), - "Prior ER Check-In to skip the line & payment at the reception.".needTranslation.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + "Prior ER Check-In to skip the line & payment at the reception." + .needTranslation + .toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), ], ), ), @@ -297,10 +292,7 @@ class EmergencyServicesPage extends StatelessWidget { showCommonBottomSheetWithoutHeight( context, child: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.primaryRedColor, - borderRadius: 24.h, - ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.primaryRedColor, borderRadius: 24.h), child: Padding( padding: EdgeInsets.all(24.h), child: Column( @@ -321,7 +313,8 @@ class EmergencyServicesPage extends StatelessWidget { }), ], ), - Lottie.asset(AppAnimations.ambulance_alert, repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain), + Lottie.asset(AppAnimations.ambulance_alert, + repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain), SizedBox(height: 8.h), LocaleKeys.confirm.tr().toText28(color: AppColors.whiteColor, isBold: true), SizedBox(height: 8.h), @@ -362,60 +355,51 @@ class EmergencyServicesPage extends StatelessWidget { ); } - openPickupDetailsBottomSheet(BuildContext context){ + openPickupDetailsBottomSheet(BuildContext context) { showCommonBottomSheetWithoutHeight( - onCloseClicked: (){ - context - .read() - .flushPickupInformation(); + onCloseClicked: () { + context.read().flushPickupInformation(); }, titleWidget: Transform.flip( - flipX: emergencyServicesViewModel.isArabic ? true : false, + flipX: emergencyServicesViewModel.isArabic, child: Utils.buildSvgWithAssets( icon: AppAssets.arrow_back, iconColor: Color(0xff2B353E), fit: BoxFit.contain, ), ).onPress(() { - context - .read() - .flushPickupInformation(); + context.read().flushPickupInformation(); Navigator.pop(context); openTranportationSelectionBottomSheet(context); }), context, - child: PickupLocation( - onTap: () { - Navigator.of(context).pop(); - context.read().flushSearchPredictions(); - context - .read() - .navigateTOAmbulancePage(); - }), + child: PickupLocation(onTap: () { + Navigator.of(context).pop(); + context.read().flushSearchPredictions(); + context.read().navigateTOAmbulancePage(); + }), isFullScreen: false, isCloseButtonVisible: true, hasBottomPadding: false, - backgroundColor: AppColors.bottomSheetBgColor, callBackFunc: () {}, ); } void openTranportationSelectionBottomSheet(BuildContext context) { - if(emergencyServicesViewModel.transportationOptions.isNotEmpty) { + if (emergencyServicesViewModel.transportationOptions.isNotEmpty) { showCommonBottomSheetWithoutHeight( title: "Transport Options".needTranslation, context, child: SizedBox( height: 400.h, - child: AmbulanceOptionSelectionBottomSheet( - onTap: () { - Navigator.of(context).pop(); - openPickupDetailsBottomSheet(context); - // context - // .read() - // .navigateTOAmbulancePage(); - }), + child: AmbulanceOptionSelectionBottomSheet(onTap: () { + Navigator.of(context).pop(); + openPickupDetailsBottomSheet(context); + // context + // .read() + // .navigateTOAmbulancePage(); + }), ), isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/emergency_services/history/er_history_listing.dart b/lib/presentation/emergency_services/history/er_history_listing.dart index e5dd1e46..b98f2f13 100644 --- a/lib/presentation/emergency_services/history/er_history_listing.dart +++ b/lib/presentation/emergency_services/history/er_history_listing.dart @@ -1,13 +1,20 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart' show AppAssets; import 'package:hmg_patient_app_new/core/app_export.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/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/OrderDisplay.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/AmbulanceRequestOrdersModel.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/RRTServiceData.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/emergency_services/history/widget/ambulance_history_item.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/history/widget/ambulance_history_item.dart' show AmbulanceHistoryItem; +import 'package:hmg_patient_app_new/presentation/emergency_services/history/widget/rrt_item.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/chip/app_custom_chip_widget.dart'; import 'package:provider/provider.dart'; class ErHistoryListing extends StatelessWidget { @@ -16,30 +23,53 @@ class ErHistoryListing extends StatelessWidget { return Scaffold( body: Column( children: [ + Expanded( child: CollapsingListView( title: "History Log".needTranslation, child: SingleChildScrollView( physics: NeverScrollableScrollPhysics(), child: Column( - children: [Visibility( - visible: context - .read() - .orders - ?.isNotEmpty == true, - child: ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: context - .read() - .orders - ?.length ?? 0, - itemBuilder: (_, index) => - Consumer( builder: (_, vm, __)=> - AmbulanceHistoryItem(order: vm.orders![index]), - ) + children: [ + + Selector( + selector: (context, vm) => (vm.orderDisplayList, vm.historyLoading), + builder: (context, data, _) { + + return Column( + children: [ + orderChips(context, data.$2, data.$1), + + Visibility( + visible:data.$1.isNotEmpty == true, + child: ListView.builder( + padding: EdgeInsets.only(top:24.h ), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount:data?.$1.length ?? 0, + itemBuilder: (_, index) { + var order = data.$1[index]; + if (order is AmbulanceRequestOrdersModel) { + return AmbulanceHistoryItem(order: order).toShimmer2(isShow: data.$2); + } else { + return RRTItem(order: (order as GetCMCAllOrdersResponseModel)).toShimmer2(isShow: data.$2); + } + }), + ), + Visibility( + visible: data.$1 + ?.isEmpty == true, child: Center( + child: Utils.getNoDataWidget(context, + noDataText: "You don't have any history" + .needTranslation), + )), + ], + ); + } ), - ),] + + + ] ).paddingAll(16.h) )) @@ -47,16 +77,77 @@ class ErHistoryListing extends StatelessWidget { ), - Visibility( - visible: context - .read() - .orders - ?.isEmpty == true, - child: Utils.getNoDataWidget(context, - noDataText: "You don't have any history" - .needTranslation)), + ], ), ); } + + orderChips(BuildContext context, bool isLoading, List dataList) { + + if (dataList?.isEmpty == true) { + return SizedBox.shrink(); + } + return Selector( + selector: (context, vm) => vm.currentlyDisplayedOrder, + builder: (context, value, __) { + return Row( + spacing: 8.h, + children: [ + if(dataList?.isNotEmpty == true) + AppCustomChipWidget( + labelText: "All Facilities".needTranslation, + shape: RoundedRectangleBorder( + side: BorderSide( + color: value == OrderDislpay.ALL ? AppColors.errorColor : AppColors.chipBorderColorOpacity20, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + backgroundColor: value == OrderDislpay.ALL ? AppColors.secondaryLightRedColor : AppColors.whiteColor, + textColor: value == OrderDislpay.ALL ? AppColors.errorColor : AppColors.blackColor, + ).onPress(() { + context.read().changeOrderDisplayItems(OrderDislpay.ALL); + }).toShimmer2(isShow: isLoading), + if(context + .read() + .ambulanceOrders + ?.isNotEmpty == true) + AppCustomChipWidget( + labelText: "Ambulance".needTranslation, + icon: AppAssets.ambulance, + shape: RoundedRectangleBorder( + side: BorderSide( + color: value == OrderDislpay.AMBULANCE ? AppColors.errorColor : AppColors.chipBorderColorOpacity20, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + backgroundColor: value == OrderDislpay.AMBULANCE ? AppColors.secondaryLightRedColor : AppColors.whiteColor, + textColor: value == OrderDislpay.AMBULANCE ? AppColors.errorColor : AppColors.blackColor, + ).toShimmer2(isShow: isLoading).onPress(() { + context.read().changeOrderDisplayItems(OrderDislpay.AMBULANCE); + }), + if(context + .read() + .ordersRRT + ?.completedOrders + .isNotEmpty == true) + AppCustomChipWidget( + labelText: "Rapid Response Team".needTranslation, + icon: AppAssets.ic_rrt_vehicle, + shape: RoundedRectangleBorder( + side: BorderSide( + color: value == OrderDislpay.RRT ? AppColors.errorColor : AppColors.chipBorderColorOpacity20, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + backgroundColor: value == OrderDislpay.RRT ? AppColors.secondaryLightRedColor : AppColors.whiteColor, + textColor: value == OrderDislpay.RRT ? AppColors.errorColor : AppColors.blackColor, + ).toShimmer2(isShow: isLoading).onPress(() { + context.read().changeOrderDisplayItems(OrderDislpay.RRT); + }), + ], + ); + }); + + } } diff --git a/lib/presentation/emergency_services/history/widget/RequestStatus.dart b/lib/presentation/emergency_services/history/widget/RequestStatus.dart index de95bdcd..4f39a495 100644 --- a/lib/presentation/emergency_services/history/widget/RequestStatus.dart +++ b/lib/presentation/emergency_services/history/widget/RequestStatus.dart @@ -54,8 +54,8 @@ class RequestStatus extends StatelessWidget { switch (status) { case 1: //pending case 2: - return AppColors.successColor;//processing case 3: //completed + return AppColors.successColor;//processing case 4: //cancel case 6: case 7: diff --git a/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart b/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart index 5f4c8b2f..f3ec3880 100644 --- a/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart +++ b/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart @@ -60,6 +60,8 @@ class AmbulanceHistoryItem extends StatelessWidget { borderColor: AppColors.primaryRedColor, textColor: Colors.white, icon: AppAssets.cancel, + height: 40.h, + iconSize: 18.w, ), ], ).paddingAll(16.h), @@ -71,7 +73,7 @@ class AmbulanceHistoryItem extends StatelessWidget { labelText: title, icon: iconString, iconColor: iconColor, - iconSize: 14.h, + iconSize: 12.h, ); } diff --git a/lib/presentation/emergency_services/history/widget/rrt_item.dart b/lib/presentation/emergency_services/history/widget/rrt_item.dart new file mode 100644 index 00000000..61eccedb --- /dev/null +++ b/lib/presentation/emergency_services/history/widget/rrt_item.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.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/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/AmbulanceRequestOrdersModel.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/RRTServiceData.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/history/widget/RequestStatus.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'; +import 'package:provider/provider.dart'; + +import '../../../../core/utils/utils.dart'; + + +class RRTItem extends StatelessWidget { + final GetCMCAllOrdersResponseModel order; + + const RRTItem({super.key, required this.order}); + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: Colors.white, + hasShadow: true, + customBorder: BorderRadius.all( + Radius.circular(20.h), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.h, + children: [ + RequestStatus(status: order.statusId ?? 0), + "Req ID: ${order.iD}".toText16(color: AppColors.textColor, weight: FontWeight.w600), + Row( + spacing: 4.w, + children: [ + chip( Utils.getDayMonthYearDateFormatted(DateUtil.convertStringToDate(order.time)), AppAssets.calendar, AppColors.blackBgColor), + chip("Rapid Response Team(RRT)".needTranslation, AppAssets.ic_rrt_vehicle, AppColors.blackBgColor), + ], + ), + SizedBox(height: 4.h), + if (order.statusId == 1 || order.statusId == 2) + CustomButton( + text: "Cancel Request".needTranslation, + onPressed: () async { + openCancelOrderBottomSheet(context); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: Colors.white, + icon: AppAssets.cancel, + iconSize: 20.h, + height: 40.h, + ), + ], + ).paddingAll(16.h), + ).paddingOnly(bottom: 16.h); + } + + chip(String title, String iconString, Color iconColor) { + return AppCustomChipWidget( + labelText: title, + icon: iconString, + iconColor: iconColor, + iconSize: 12.h, + ); + } + + openCancelOrderBottomSheet(BuildContext context) { + context.read().cancelRRTOrder(order.iD); + } +} diff --git a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart index b4147994..8be6ce29 100644 --- a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart +++ b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart @@ -90,7 +90,7 @@ class _WalletPaymentConfirmPageState extends State { Transform.flip( flipX: appState.isArabic(), child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, + icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.blackColor, width: 18.h, height: 13.h, @@ -132,7 +132,7 @@ class _WalletPaymentConfirmPageState extends State { Transform.flip( flipX: appState.isArabic(), child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, + icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.blackColor, width: 18.h, height: 13.h, diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index f79aae02..af576aa6 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -1,22 +1,78 @@ import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/hmg_services_component_model.dart'; +import 'package:hmg_patient_app_new/presentation/hmg_services/services_view.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; class ServicesPage extends StatelessWidget { - const ServicesPage({super.key}); + ServicesPage({super.key}); + + final List hmgServices = [ + HmgServicesComponentModel( + 11, + "E Referral Services".needTranslation, + "".needTranslation, + AppAssets.eReferral, + true, + bgColor: Colors.orange, + textColor: AppColors.blackColor, + route: AppRoutes.eReferralPage, + ), + HmgServicesComponentModel( + 12, + "Comprehensive Checkup".needTranslation, + "".needTranslation, + AppAssets.comprehensiveCheckup, + true, + bgColor: AppColors.bgGreenColor, + textColor: AppColors.blackColor, + route: AppRoutes.comprehensiveCheckupPage, + ), + HmgServicesComponentModel( + 12, + "Home Health Care".needTranslation, + "".needTranslation, + AppAssets.emergency_services_icon, + true, + bgColor: AppColors.bgGreenColor, + textColor: AppColors.blackColor, + route: AppRoutes.homeHealthCarePage, + ), + ]; @override Widget build(BuildContext context) { return CollapsingListView( title: "Explore Services".needTranslation, - isLeading: false, + isLeading: Navigator.canPop(context), child: Padding( padding: EdgeInsets.all(24.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Medical & Care Services".needTranslation.toText18(isBold: true) + "Medical & Care Services".needTranslation.toText18(isBold: true), + SizedBox(height: 20.h), + Padding( + padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 0), + child: GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, // 4 icons per row + crossAxisSpacing: 16.w, + mainAxisSpacing: 24.h, + childAspectRatio: 0.75, + ), + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: hmgServices.length, + padding: EdgeInsets.zero, + itemBuilder: (BuildContext context, int index) { + return ServiceGridViewItem(hmgServices[index], index, false); + }, + ), + ) ], ), ), diff --git a/lib/presentation/hmg_services/services_view.dart b/lib/presentation/hmg_services/services_view.dart new file mode 100644 index 00000000..225bd963 --- /dev/null +++ b/lib/presentation/hmg_services/services_view.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/dependencies.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/features/hmg_services/models/ui_models/hmg_services_component_model.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; + +class ServiceGridViewItem extends StatelessWidget { + final HmgServicesComponentModel hmgServiceComponentModel; + final int index; + final bool isHomePage; + final bool isLocked; + + const ServiceGridViewItem(this.hmgServiceComponentModel, this.index, this.isHomePage, {super.key, this.isLocked = false}); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () => getIt.get().pushPageRoute(hmgServiceComponentModel.route), + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 48.h, + width: 48.w, + padding: EdgeInsets.zero, + margin: EdgeInsets.zero, + decoration: BoxDecoration( + color: hmgServiceComponentModel.bgColor, + borderRadius: BorderRadius.circular(12.r), + ), + child: Utils.buildSvgWithAssets( + icon: hmgServiceComponentModel.icon, + height: 21.h, + width: 21.w, + fit: BoxFit.none, + ), + ), + SizedBox(height: 5.h), + hmgServiceComponentModel.title.toText12( + fontWeight: FontWeight.w500, + color: hmgServiceComponentModel.textColor, + maxLine: 1, + ), + ], + )); + } +} diff --git a/lib/presentation/home/data/landing_page_data.dart b/lib/presentation/home/data/landing_page_data.dart index 31b4598b..3d74cfdb 100644 --- a/lib/presentation/home/data/landing_page_data.dart +++ b/lib/presentation/home/data/landing_page_data.dart @@ -81,7 +81,7 @@ class LandingPageData { ServiceCardData( serviceName: "lab_results", icon: AppAssets.home_lab_result_icon, - title: "My Lab", + title: "Lab", subtitle: "Results", backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, @@ -91,7 +91,7 @@ class LandingPageData { ServiceCardData( serviceName: "radiology_results", icon: AppAssets.home_lab_result_icon, - title: "My Radiology", + title: "Radiology", subtitle: "Results", backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, @@ -101,8 +101,8 @@ class LandingPageData { ServiceCardData( serviceName: "prescriptions", icon: AppAssets.my_prescription_icon, - title: "My", - subtitle: "Prescriptions", + title: "Prescriptions", + subtitle: "Details", backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -112,7 +112,7 @@ class LandingPageData { serviceName: "insurance_update", icon: AppAssets.insurance_update_icon, title: "Insurance", - subtitle: "Update", + subtitle: "Details", backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -131,7 +131,7 @@ class LandingPageData { ServiceCardData( serviceName: "sick_leaves", icon: AppAssets.insurance_update_icon, - title: "My Sick", + title: "Sick", subtitle: "Leaves", backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 33479027..ce2e5140 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -31,6 +31,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointme import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart'; import 'package:hmg_patient_app_new/presentation/contact_us/contact_us.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart'; +import 'package:hmg_patient_app_new/presentation/hmg_services/services_page.dart'; import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; @@ -160,12 +161,12 @@ class _LandingPageState extends State { ); }), Utils.buildSvgWithAssets(icon: AppAssets.search_icon, height: 18.h, width: 18.h).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: MedicalFilePage(), - // page: LoginScreen(), - ), - ); + // Navigator.of(context).push( + // CustomPageRoute( + // page: MedicalFilePage(), + // // page: LoginScreen(), + // ), + // ); }), Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 18.h, width: 18.h).onPress(() { showCommonBottomSheetWithoutHeight( @@ -245,10 +246,10 @@ class _LandingPageState extends State { indicatorLayout: PageIndicatorLayout.COLOR, axisDirection: AxisDirection.right, controller: _controller, - itemHeight: 210 + 25, - pagination: const SwiperPagination( + itemHeight: 270.h, + pagination: SwiperPagination( alignment: Alignment.bottomCenter, - margin: EdgeInsets.only(top: 210 + 8 + 24), + margin: EdgeInsets.only(top: 250.h + 8 + 24), builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor), ), itemBuilder: (BuildContext context, int index) { @@ -303,7 +304,6 @@ class _LandingPageState extends State { ).paddingSymmetrical(24.h, 0.h); }, ), - // Consumer for LiveCare pending request Consumer( builder: (context, immediateLiveCareVM, child) { @@ -421,7 +421,6 @@ class _LandingPageState extends State { : SizedBox(height: 12.h); }, ), - Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -439,7 +438,7 @@ class _LandingPageState extends State { }), SizedBox(height: 16.h), Container( - height: 120.h, + height: 121.h, decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), child: Column( children: [ @@ -526,11 +525,13 @@ class _LandingPageState extends State { SizedBox(width: 2.h), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], - ), + ).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: ServicesPage())); + }), ], ).paddingSymmetrical(24.h, 0.h), SizedBox( - height: 280.h, + height: 340.h, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: LandingPageData.getServiceCardsList.length, @@ -567,9 +568,8 @@ class _LandingPageState extends State { void showQuickLogin(BuildContext context) { showCommonBottomSheetWithoutHeight( context, - title: "", + // title: "", isCloseButtonVisible: false, - child: StatefulBuilder( builder: (context, setState) { return QuickLogin( diff --git a/lib/presentation/home/navigation_screen.dart b/lib/presentation/home/navigation_screen.dart index 9c7566dd..18803cc0 100644 --- a/lib/presentation/home/navigation_screen.dart +++ b/lib/presentation/home/navigation_screen.dart @@ -2,10 +2,11 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; +import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart'; import 'package:hmg_patient_app_new/presentation/hmg_services/services_page.dart'; import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; -import 'package:hmg_patient_app_new/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 { @@ -29,7 +30,7 @@ class _LandingNavigationState extends State { physics: const NeverScrollableScrollPhysics(), children: [ const LandingPage(), - appState.isAuthenticated ? MedicalFilePage() : /* need add feedback page */ const LandingPage(), + appState.isAuthenticated ? MedicalFilePage() : /* need add feedback page */ FeedbackPage(), BookAppointmentPage(), const ToDoPage(), appState.isAuthenticated ? /* need add news page */ ServicesPage() : const LandingPage(), diff --git a/lib/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index 234fad13..f54f442a 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -110,6 +110,7 @@ class SmallServiceCard extends StatelessWidget { case "emergency": context.read().flushData(); context.read().getTransportationOrders(showLoader: false,); + context.read().getRRTOrders(showLoader: false,); Navigator.of(context).push( CustomPageRoute( page: EmergencyServicesPage(), diff --git a/lib/presentation/home_health_care/hhc_order_detail_page.dart b/lib/presentation/home_health_care/hhc_order_detail_page.dart new file mode 100644 index 00000000..21b0defa --- /dev/null +++ b/lib/presentation/home_health_care/hhc_order_detail_page.dart @@ -0,0 +1,248 @@ +import 'dart:async'; + +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/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; +import 'package:hmg_patient_app_new/presentation/home_health_care/widgets/hhc_ui_selection_helper.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:provider/provider.dart'; + +class HhcOrderDetailPage extends StatefulWidget { + const HhcOrderDetailPage({super.key}); + + @override + State createState() => _HhcOrderDetailPageState(); +} + +class _HhcOrderDetailPageState extends State { + @override + void initState() { + super.initState(); + final hmgServicesViewModel = context.read(); + scheduleMicrotask(() async { + await hmgServicesViewModel.getAllHhcOrders(); + }); + } + + Color _getStatusColor(int? statusId) { + switch (statusId) { + case 1: // Pending + return const Color(0xffCC9B14); + case 2: // Processing + return const Color(0xff2E303A); + case 3: // Completed + return const Color(0xff359846); + case 4: // Cancelled + case 6: // Rejected + case 7: // Rejected + return const Color(0xffD02127); + default: + return AppColors.greyColor; + } + } + + String _formatDate(String? dateString) { + if (dateString == null) return ''; + try { + final date = DateTime.parse(dateString); + return DateFormat('MMM dd, yyyy').format(date); + } catch (e) { + return dateString; + } + } + + Widget _buildLoadingShimmer() { + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: 3, + separatorBuilder: (_, __) => SizedBox(height: 12.h), + itemBuilder: (context, index) { + return _buildOrderCard(GetCMCAllOrdersResponseModel(), isLoading: true); + }, + ); + } + + Widget _buildOrderCard(GetCMCAllOrdersResponseModel order, {bool isLoading = false}) { + final statusColor = _getStatusColor(order.statusId); + final canCancel = order.statusId == 1 || order.statusId == 2; + + return AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status and Date Row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h), + decoration: BoxDecoration( + color: statusColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8.r), + ), + child: (isLoading ? "Processing" : order.statusText ?? '') + .toText12( + color: statusColor, + fontWeight: FontWeight.w600, + ) + .toShimmer2(isShow: isLoading), + ), + SizedBox(width: 8.w), + (isLoading ? "Jan 15, 2024" : _formatDate(order.created)) + .toText12( + color: AppColors.textColorLight, + fontWeight: FontWeight.w500, + ) + .toShimmer2(isShow: isLoading), + ], + ), + + SizedBox(height: 16.h), + + // Request ID + Row( + children: [ + if (!isLoading) ...[ + "Request ID:".needTranslation.toText14( + color: AppColors.textColorLight, + weight: FontWeight.w500, + ), + SizedBox(width: 4.w), + ], + (isLoading ? "12345" : "${order.iD ?? '-'}").toText16(isBold: true).toShimmer2(isShow: isLoading), + ], + ), + + SizedBox(height: 12.h), + + // Chips for Hospital, Service, and Amount + Wrap( + spacing: 6.w, + runSpacing: 6.h, + children: [ + // Service + if (order.serviceText != null || isLoading) + AppCustomChipWidget( + icon: AppAssets.servicesBottom, + labelText: isLoading ? "Service Name" : order.serviceText ?? '-', + ).toShimmer2(isShow: isLoading), + ], + ), + + // Cancel Button + if (canCancel || isLoading) ...[ + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: "Cancel Order".needTranslation, + onPressed: isLoading ? () {} : () => HhcUiSelectionHelper.showCancelConfirmationDialog(context: context, order: order), + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 10.r, + height: 44.h, + ).toShimmer2(isShow: isLoading), + ), + ], + ), + ] + ], + ), + ), + ); + } + + Widget _buildEmptyState() { + 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 Home Health Care orders yet.".needTranslation, + isSmallWidget: true, + width: 62.w, + height: 62.h, + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: "HHC Orders".needTranslation, + isLeading: true, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Consumer( + builder: (context, viewModel, child) { + if (viewModel.isHhcOrdersLoading) { + return _buildLoadingShimmer(); + } + + if (viewModel.hhcOrdersList.isEmpty) { + return _buildEmptyState(); + } + + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: viewModel.hhcOrdersList.length, + separatorBuilder: (_, __) => SizedBox(height: 12.h), + itemBuilder: (context, index) { + final order = viewModel.hhcOrdersList.reversed.toList()[index]; + + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: _buildOrderCard(order), + ), + ), + ); + }, + ); + }, + ), + ], + ).paddingSymmetrical(24.w, 0), + ), + ); + } +} diff --git a/lib/presentation/home_health_care/hhc_procedures_page.dart b/lib/presentation/home_health_care/hhc_procedures_page.dart new file mode 100644 index 00000000..0cf57ceb --- /dev/null +++ b/lib/presentation/home_health_care/hhc_procedures_page.dart @@ -0,0 +1,414 @@ +import 'dart:async'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/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/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; +import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_order_detail_page.dart'; +import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_selection_review_page.dart'; +import 'package:hmg_patient_app_new/presentation/home_health_care/widgets/hhc_ui_selection_helper.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/routes/custom_page_route.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +class HhcProceduresPage extends StatefulWidget { + const HhcProceduresPage({super.key}); + + @override + State createState() => _HhcProceduresPageState(); +} + +class _HhcProceduresPageState extends State { + @override + void initState() { + super.initState(); + final HmgServicesViewModel hmgServicesViewModel = context.read(); + final AppState appState = getIt.get(); + + scheduleMicrotask(() async { + final user = appState.getAuthenticatedUser(); + if (user != null) { + // Clear previous selections when entering the page + hmgServicesViewModel.clearHhcServicesSelection(); + await hmgServicesViewModel.getAllHhcOrders(); + await hmgServicesViewModel.getAllHhcServices(patientID: user.patientId ?? 0); + } + }); + } + + GetCMCAllOrdersResponseModel? _getPendingOrder(List orders) { + if (orders.isEmpty) return null; + + // Find pending or processing orders (status 1 or 2) + for (var order in orders) { + if (order.statusId == 1 || order.statusId == 2) { + return order; + } + } + + return null; + } + + Widget _buildPendingOrderCard(GetCMCAllOrdersResponseModel order) { + int status = order.statusId ?? 0; + String statusDisp = order.statusText ?? ""; + Color statusColor; + + if (status == 1) { + // pending + statusColor = AppColors.statusPendingColor; + } else if (status == 2) { + // processing + statusColor = AppColors.statusProcessingColor; + } else if (status == 3) { + // completed + statusColor = AppColors.statusCompletedColor; + } else { + // cancel / rejected + statusColor = AppColors.statusRejectedColor; + } + + final canCancel = order.statusId == 1 || order.statusId == 2; + + return Container( + width: double.infinity, + margin: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status and Date Row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h), + decoration: BoxDecoration( + color: statusColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8.r), + ), + child: statusDisp.toText12( + color: statusColor, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(width: 8.w), + if (order.created != null) + DateFormat('MMM dd, yyyy').format(DateTime.parse(order.created!)).toText12( + color: AppColors.textColorLight, + fontWeight: FontWeight.w500, + ), + ], + ), + + SizedBox(height: 16.h), + + // Request ID + Row( + children: [ + "Request ID:".needTranslation.toText14(color: AppColors.textColorLight, weight: FontWeight.w500), + SizedBox(width: 4.w), + "${order.iD ?? '-'}".toText16(isBold: true), + ], + ), + + SizedBox(height: 12.h), + + // Info message + Container( + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + color: AppColors.infoBannerBgColor, + borderRadius: BorderRadius.circular(10.r), + border: Border.all( + color: AppColors.infoBannerBorderColor, + width: 1, + ), + ), + child: Row( + children: [ + Icon( + Icons.info_outline, + size: 20.w, + color: AppColors.infoBannerIconColor, + ), + SizedBox(width: 8.w), + Expanded( + child: "You have a pending order. Please wait for it to be processed.".needTranslation.toText12( + color: AppColors.infoBannerTextColor, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + if (canCancel) ...[ + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: "Cancel Order".needTranslation, + onPressed: () => HhcUiSelectionHelper.showCancelConfirmationDialog(context: context, order: order), + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 10.r, + height: 44.h, + ), + ), + ], + ), + ] + ], + ), + ), + ); + } + + Widget _buildServiceSelectionList(List services) { + if (services.isEmpty) { + return Center( + child: Padding( + padding: EdgeInsets.all(24.h), + child: Text( + 'No services available'.needTranslation, + style: TextStyle( + fontSize: 16.h, + color: AppColors.greyTextColor, + ), + ), + ), + ); + } + + return Consumer( + builder: (context, viewModel, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 8.h), + if (viewModel.selectedHhcServices.isNotEmpty) ...[ + SizedBox(height: 16.h), + Container( + margin: EdgeInsets.symmetric(horizontal: 16.w), + padding: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.primaryRedColor.withValues(alpha: 0.1), + borderRadius: 16.r, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Selected Services".needTranslation.toText12( + color: AppColors.textColorLight, + fontWeight: FontWeight.w600, + ), + "${viewModel.selectedHhcServices.length} service(s) selected".toText14( + isBold: true, + weight: FontWeight.bold, + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + "Total Amount".needTranslation.toText12( + color: AppColors.textColorLight, + fontWeight: FontWeight.w600, + ), + Utils.getPaymentAmountWithSymbol( + viewModel.getHhcSelectedServicesTotal().toStringAsFixed(2).toText16( + isBold: true, + weight: FontWeight.bold, + color: AppColors.primaryRedColor, + ), + AppColors.primaryRedColor, + 14, + isSaudiCurrency: true, + ), + ], + ), + ], + ), + ), + ], + + SizedBox(height: 16.h), + Text( + 'Select Services'.needTranslation, + style: TextStyle( + fontSize: 20.h, + fontWeight: FontWeight.w700, + color: AppColors.blackColor, + letterSpacing: -0.8, + ), + ).paddingOnly(left: 16.w, right: 16.w), + SizedBox(height: 12.h), + ListView.builder( + padding: EdgeInsets.symmetric(horizontal: 16.w), + itemCount: services.length, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + final service = services[index]; + final isSelected = viewModel.isHhcServiceSelected(service); + final isArabic = getIt.get().isArabic(); + final serviceName = isArabic ? (service.textN ?? service.text ?? '') : (service.text ?? ''); + + return AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.only(bottom: 12.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 16.r, + hasShadow: true, + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => viewModel.toggleHhcServiceSelection(service), + borderRadius: BorderRadius.circular(16.r), + child: Container( + padding: EdgeInsets.all(16.w), + child: Row( + children: [ + Checkbox( + value: isSelected, + onChanged: (v) => viewModel.toggleHhcServiceSelection(service), + activeColor: AppColors.primaryRedColor, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + visualDensity: VisualDensity.compact, + ), + SizedBox(width: 12.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + serviceName.toText16( + weight: FontWeight.w400, + color: AppColors.blackColor, + maxlines: 2, + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + }, + ), + // Summary Section + ], + ); + }, + ); + } + + void _proceedWithSelectedService() { + final hmgServicesViewModel = context.read(); + if (hmgServicesViewModel.selectedHhcServices.isNotEmpty) { + hmgServicesViewModel.setSelectedServiceForHhcOrder(hmgServicesViewModel.selectedHhcServices.first); + Navigator.of(context).pushReplacement( + CustomPageRoute( + page: HhcSelectionReviewPage(selectedServices: hmgServicesViewModel.selectedHhcServices), + direction: AxisDirection.left, + ), + ); + } + } + + Widget _buildLoadingShimmer() { + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: EdgeInsets.all(16.w), + itemCount: 10, + separatorBuilder: (_, __) => SizedBox(height: 12.h), + itemBuilder: (context, index) { + return Shimmer.fromColors( + baseColor: Colors.grey[300]!, + highlightColor: Colors.grey[100]!, + child: Container( + height: 80.h, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10.r), + ), + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: "Home Health Care".needTranslation, + history: () => Navigator.of(context).push(CustomPageRoute(page: HhcOrderDetailPage(), direction: AxisDirection.up)), + bottomChild: Consumer( + builder: (context, hmgServicesViewModel, child) { + if (hmgServicesViewModel.isHhcOrdersLoading || hmgServicesViewModel.isHhcServicesLoading) return SizedBox.shrink(); + final pendingOrder = _getPendingOrder(hmgServicesViewModel.hhcOrdersList); + if (pendingOrder == null && hmgServicesViewModel.selectedHhcServices.isNotEmpty) { + return SafeArea( + top: false, + child: Padding( + padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 12.h), + child: CustomButton( + borderWidth: 0, + text: "Next".needTranslation, + onPressed: _proceedWithSelectedService, + textColor: AppColors.whiteColor, + borderRadius: 12.r, + borderColor: Colors.transparent, + padding: EdgeInsets.symmetric(vertical: 14.h), + ), + ), + ); + } + return SizedBox.shrink(); + }, + ), + child: Consumer( + builder: (context, hmgServicesViewModel, child) { + if (hmgServicesViewModel.isHhcOrdersLoading || hmgServicesViewModel.isHhcServicesLoading) { + return _buildLoadingShimmer(); + } + final pendingOrder = _getPendingOrder(hmgServicesViewModel.hhcOrdersList); + if (pendingOrder != null) { + return _buildPendingOrderCard(pendingOrder); + } else { + return _buildServiceSelectionList(hmgServicesViewModel.hhcServicesList); + } + }, + ), + ); + } +} diff --git a/lib/presentation/home_health_care/hhc_selection_review_page.dart b/lib/presentation/home_health_care/hhc_selection_review_page.dart new file mode 100644 index 00000000..7baeec02 --- /dev/null +++ b/lib/presentation/home_health_care/hhc_selection_review_page.dart @@ -0,0 +1,244 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/route_extensions.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/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/cmc_create_new_order_req_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:maps_launcher/maps_launcher.dart'; +import 'package:provider/provider.dart'; + +class HhcSelectionReviewPage extends StatefulWidget { + final List selectedServices; + + const HhcSelectionReviewPage({super.key, required this.selectedServices}); + + @override + State createState() => _HhcSelectionReviewPageState(); +} + +class _HhcSelectionReviewPageState extends State { + @override + void initState() { + super.initState(); + // Initialize ViewModel state with selected services + WidgetsBinding.instance.addPostFrameCallback((_) { + final hmgServicesViewModel = context.read(); + if (widget.selectedServices.isNotEmpty) { + hmgServicesViewModel.setSelectedServiceForHhcOrder(widget.selectedServices.first); + } + }); + } + + @override + Widget build(BuildContext context) { + final appState = getIt.get(); + final isArabic = appState.isArabic(); + + return CollapsingListView( + title: "Summary".needTranslation, + bottomChild: _buildBottomButton(), + child: SingleChildScrollView( + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSelectedServicesCard(isArabic), + SizedBox(height: 16.h), + ], + ), + ), + ); + } + + Widget _buildSelectedServicesCard(bool isArabic) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 16.r, + ), + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Selected Services".needTranslation.toText14( + weight: FontWeight.w600, + color: AppColors.greyTextColor, + letterSpacing: -0.4, + ), + SizedBox(height: 12.h), + ...widget.selectedServices.map((service) { + final serviceName = isArabic ? (service.textN ?? service.text ?? '') : (service.text ?? ''); + final price = service.priceTotal ?? 0.0; + return Padding( + padding: EdgeInsets.only(bottom: 4.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: serviceName.toText14( + weight: FontWeight.w600, + color: AppColors.blackColor, + letterSpacing: -0.5, + maxlines: 2, + ), + ), + ], + ), + ); + }), + ], + ), + ); + } + + Widget _buildBottomButton() { + return SafeArea( + top: false, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + decoration: BoxDecoration( + color: AppColors.whiteColor, + boxShadow: [ + BoxShadow( + color: Color.fromARGB(13, 0, 0, 0), + blurRadius: 8, + offset: Offset(0, -2), + ), + ], + ), + child: CustomButton( + text: "Confirm".needTranslation, + onPressed: _handleConfirm, + textColor: AppColors.whiteColor, + backgroundColor: AppColors.successColor, + borderRadius: 12.r, + borderColor: Colors.transparent, + borderWidth: 0, + padding: EdgeInsets.symmetric(vertical: 14.h), + ), + ), + ); + } + + void _launchDirections(HospitalsModel selectedHospital) { + final double lat = double.parse(selectedHospital.latitude ?? "0.0"); + final double lng = double.parse(selectedHospital.longitude ?? "0.0"); + + if (lat != 0.0 && lng != 0.0) { + MapsLauncher.launchCoordinates( + lat, + lng, + selectedHospital.name ?? "Hospital", + ); + } + } + + showSuccessBottomSheet(int requestId, HmgServicesViewModel hmgServicesViewModel) { + return showCommonBottomSheetWithoutHeight( + context, + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + children: [ + Utils.getSuccessWidget(loadingText: "Your request has been successfully submitted.".needTranslation), + Row( + children: [ + "Here is your request #: ".needTranslation.toText14( + color: AppColors.textColorLight, + weight: FontWeight.w500, + ), + SizedBox(width: 4.w), + ("$requestId").toText16(isBold: true), + ], + ), + SizedBox(height: 24.h), + Row( + children: [ + Expanded( + child: CustomButton( + height: 56.h, + text: LocaleKeys.ok.tr(), + onPressed: () { + context.pop(); + context.pop(); + hmgServicesViewModel.getAllHhcOrders(); + }, + textColor: AppColors.whiteColor, + ), + ), + ], + ), + ], + ), + ), + isCloseButtonVisible: false, + isDismissible: false, + isFullScreen: false, + ); + } + + void _handleConfirm() { + final hmgServicesViewModel = context.read(); + final appState = getIt.get(); + + return showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: "Are you sure you want to submit this request?".needTranslation, + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + Navigator.pop(context); + LoaderBottomSheet.showLoader(); + + // Create the services list from all selected services + final servicesList = widget.selectedServices.map((selectedService) { + return PatientERCMCInsertServicesList( + recordID: selectedService.iD, + serviceID: selectedService.serviceID, + selectedServiceName: selectedService.text, + selectedServiceNameAR: selectedService.textN, + price: selectedService.price, + vAT: selectedService.priceVAT, + totalPrice: selectedService.priceTotal, + ); + }).toList(); + + // For HHC, we don't need hospital selection, use a default projectID or 0 + await hmgServicesViewModel.addHhcOrder( + projectID: 0, + // HHC doesn't require hospital/project selection + orderServiceID: widget.selectedServices.first.orderServiceID ?? 4, + // HHC service ID + services: servicesList, + onSuccess: (requestId) { + LoaderBottomSheet.hideLoader(); + showSuccessBottomSheet(requestId, hmgServicesViewModel); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + }, + ); + }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } +} diff --git a/lib/presentation/home_health_care/widgets/hhc_ui_selection_helper.dart b/lib/presentation/home_health_care/widgets/hhc_ui_selection_helper.dart new file mode 100644 index 00000000..688612c8 --- /dev/null +++ b/lib/presentation/home_health_care/widgets/hhc_ui_selection_helper.dart @@ -0,0 +1,90 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.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/route_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/order_update_req_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.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/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:provider/provider.dart'; + +class HhcUiSelectionHelper { + static void showCancelConfirmationDialog({ + required BuildContext context, + required GetCMCAllOrdersResponseModel order, + }) { + final HmgServicesViewModel hmgServicesViewModel = context.read(); + + return showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: "Are you sure you want to cancel this order?".needTranslation, + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + Navigator.pop(context); + LoaderBottomSheet.showLoader(); + + final requestModel = OrderUpdateRequestModel( + presOrderID: order.iD, + rejectionReason: "Cancelled by user", + presOrderStatus: 4, // Cancelled status + editedBy: 3, + ); + + await hmgServicesViewModel.updateHhcPresOrder( + requestModel: requestModel, + onSuccess: (_) async { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + children: [ + Utils.getSuccessWidget(loadingText: "Order has been cancelled successfully".needTranslation), + SizedBox(height: 24.h), + Row( + children: [ + Expanded( + child: CustomButton( + height: 56.h, + text: LocaleKeys.ok.tr(), + onPressed: () { + context.pop(); + hmgServicesViewModel.getAllHhcOrders(); + }, + textColor: AppColors.whiteColor, + ), + ), + ], + ), + ], + ), + ), + isCloseButtonVisible: false, + isDismissible: false, + isFullScreen: false, + ); + }, + onError: (error) { + LoaderBottomSheet.hideLoader(); + }, + ); + }, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } +} diff --git a/lib/presentation/insurance/insurance_home_page.dart b/lib/presentation/insurance/insurance_home_page.dart index bd195c3e..cdd9a2ee 100644 --- a/lib/presentation/insurance/insurance_home_page.dart +++ b/lib/presentation/insurance/insurance_home_page.dart @@ -20,7 +20,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.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/shimmer/movies_shimmer_widget.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/common_shimmer_widget.dart'; import 'package:provider/provider.dart'; import 'widgets/insurance_history.dart'; diff --git a/lib/presentation/insurance/widgets/insurance_history.dart b/lib/presentation/insurance/widgets/insurance_history.dart index 1c7b1b96..a5114dd9 100644 --- a/lib/presentation/insurance/widgets/insurance_history.dart +++ b/lib/presentation/insurance/widgets/insurance_history.dart @@ -12,7 +12,7 @@ import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.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'; -import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/common_shimmer_widget.dart'; import 'package:provider/provider.dart'; class InsuranceHistory extends StatelessWidget { diff --git a/lib/presentation/insurance/widgets/insurance_update_details_card.dart b/lib/presentation/insurance/widgets/insurance_update_details_card.dart index acdf1c7e..753a36ca 100644 --- a/lib/presentation/insurance/widgets/insurance_update_details_card.dart +++ b/lib/presentation/insurance/widgets/insurance_update_details_card.dart @@ -12,7 +12,7 @@ import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.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'; -import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/common_shimmer_widget.dart'; import 'package:provider/provider.dart'; class PatientInsuranceCardUpdateCard extends StatelessWidget { diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 0b9d0933..4ffd9790 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -1 +1 @@ -import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import '../../widgets/appbar/collapsing_list_view.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: SingleChildScrollView( padding: EdgeInsets.all(24.h), physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), SizedBox(height: 16.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.isNotEmpty ? model.patientLabOrders.length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.patientLabOrders.isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabResultItemView( onTap: () { model.currentlySelectedPatientOrder = model.patientLabOrders[ index]; labProvider.getPatientLabResultByHospital(model.patientLabOrders[ index]); labProvider .getPatientSpecialResult( model.patientLabOrders[ index]); Navigator.push( context, CustomPageRoute( page: LabResultByClinic(labOrder: model.patientLabOrders[index]), )); }, labOrder: model.patientLabOrders[index], index: index, isExpanded: isExpanded), ), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) : ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.uniqueTests.toList().isNotEmpty ? model.uniqueTests.toList().length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.uniqueTests.toList().isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabOrderByTest( appState: _appState, onTap: () { if (model.uniqueTests.toList()[index].model != null) { rangeViewModel.flush(); model.getPatientLabResult(model.uniqueTests.toList()[index].model!, model.uniqueTests.toList()[index].description!, (_appState.isArabic() ? model.uniqueTests.toList()[index].testDescriptionAr! : model.uniqueTests.toList()[index].testDescriptionEn!)); } }, tests: model.uniqueTests.toList()[index], index: index, isExpanded: isExpanded)), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) ], ); }, ), ), )); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file +import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import '../../widgets/appbar/collapsing_list_view.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: SingleChildScrollView( padding: EdgeInsets.all(24.h), physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.isNotEmpty ? model.patientLabOrders.length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.patientLabOrders.isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabResultItemView( onTap: () { model.currentlySelectedPatientOrder = model.patientLabOrders[ index]; labProvider.getPatientLabResultByHospital(model.patientLabOrders[ index]); labProvider .getPatientSpecialResult( model.patientLabOrders[ index]); Navigator.push( context, CustomPageRoute( page: LabResultByClinic(labOrder: model.patientLabOrders[index]), )); }, labOrder: model.patientLabOrders[index], index: index, isExpanded: isExpanded), ), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) : ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.uniqueTests.toList().isNotEmpty ? model.uniqueTests.toList().length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.uniqueTests.toList().isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabOrderByTest( appState: _appState, onTap: () { if (model.uniqueTests.toList()[index].model != null) { rangeViewModel.flush(); model.getPatientLabResult(model.uniqueTests.toList()[index].model!, model.uniqueTests.toList()[index].description!, (_appState.isArabic() ? model.uniqueTests.toList()[index].testDescriptionAr! : model.uniqueTests.toList()[index].testDescriptionEn!)); } }, tests: model.uniqueTests.toList()[index], index: index, isExpanded: isExpanded)), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) ], ); }, ), ), )); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file diff --git a/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart b/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart index 36cdc2b6..bc1d6b17 100644 --- a/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart +++ b/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart @@ -46,7 +46,7 @@ class LabOrderResultItem extends StatelessWidget { padding: EdgeInsets.only(bottom: 8.h), child: '${tests!.description}'.toText14(weight: FontWeight.w500), ), - '${tests!.packageShortDescription}'.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + (tests!.packageShortDescription ?? "").toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), SizedBox(height: 12.h), Row( mainAxisSize: MainAxisSize.max, @@ -58,22 +58,25 @@ class LabOrderResultItem extends StatelessWidget { fontSize: 24.f, fontWeight: FontWeight.w600, fontFamily: 'Poppins', - color: context.read().getColor( - tests?.calculatedResultFlag ?? "", - ), + color: tests!.checkIfGraphShouldBeDisplayed() + ? context.read().getColor( + tests?.calculatedResultFlag ?? "", + ) + : Colors.grey.shade700, letterSpacing: -2, ), ), ), SizedBox(width: 4.h,), Visibility( - visible: tests?.checkIfGraphShouldBeDisplayed() == true, + // visible: tests?.checkIfGraphShouldBeDisplayed() == true, + visible: true, child: Expanded( flex: 2, child: Visibility( visible: tests?.referanceRange != null, child: Text( - "(Reference range ${tests?.referanceRange})".needTranslation, + "(Reference range: ${tests?.referanceRange})".needTranslation, style: TextStyle( fontSize: 12.f, fontWeight: FontWeight.w500, diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 828cbb4e..2cf584e0 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -26,6 +26,7 @@ import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_mo import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor_request_type_select.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; @@ -56,7 +57,7 @@ import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.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:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/common_shimmer_widget.dart'; import 'package:provider/provider.dart'; import '../prescriptions/prescription_detail_page.dart'; @@ -174,8 +175,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, @@ -222,13 +222,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( @@ -237,7 +237,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), ); }), @@ -344,7 +345,7 @@ class _MedicalFilePageState extends State { Consumer(builder: (context, myAppointmentsVM, child) { // Provide an explicit height so the horizontal ListView has a bounded height return SizedBox( - height: 190.h, + height: 192.h, child: myAppointmentsVM.isMyAppointmentsLoading ? MedicalFileAppointmentCard( patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), @@ -407,7 +408,34 @@ class _MedicalFilePageState extends State { onRescheduleTap: () { openDoctorScheduleCalendar(myAppointmentsVM.patientAppointmentsHistoryList[index]); }, - onAskDoctorTap: () {}, + onAskDoctorTap: () async { + LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation); + await myAppointmentsViewModel.isDoctorAvailable( + projectID: myAppointmentsVM.patientAppointmentsHistoryList[index].projectID, + doctorId: myAppointmentsVM.patientAppointmentsHistoryList[index].doctorID, + clinicId: myAppointmentsVM.patientAppointmentsHistoryList[index].clinicID, + onSuccess: (value) async { + if (value) { + await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.askDoctor.tr(context: context), + child: AskDoctorRequestTypeSelect( + askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList, + myAppointmentsViewModel: myAppointmentsViewModel, + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + print("Doctor is not available"); + } + }); + }, )), ), ), @@ -460,7 +488,7 @@ class _MedicalFilePageState extends State { SizedBox(height: 16.h), Consumer(builder: (context, prescriptionVM, child) { return prescriptionVM.isPrescriptionsOrdersLoading - ? const MoviesShimmerWidget().paddingSymmetrical(24.w, 0.h) + ? const CommonShimmerWidget().paddingSymmetrical(24.w, 0.h) : prescriptionVM.patientPrescriptionOrders.isNotEmpty ? Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -472,7 +500,7 @@ class _MedicalFilePageState extends State { child: Column( children: [ ListView.separated( - itemCount: prescriptionVM.patientPrescriptionOrders.length, + itemCount: prescriptionVM.patientPrescriptionOrders.length <= 2 ? prescriptionVM.patientPrescriptionOrders.length : 2, shrinkWrap: true, padding: EdgeInsets.only(left: 0, right: 8.w), physics: NeverScrollableScrollPhysics(), @@ -533,6 +561,7 @@ class _MedicalFilePageState extends State { Navigator.of(context).push( CustomPageRoute( page: PrescriptionDetailPage( + isFromAppointments: false, prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]), ), ); @@ -543,9 +572,9 @@ class _MedicalFilePageState extends State { }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), - SizedBox(height: 24.h), + SizedBox(height: 8.h), const Divider(color: AppColors.dividerColor), - SizedBox(height: 24.h), + SizedBox(height: 8.h), Row( children: [ Expanded( @@ -564,7 +593,7 @@ class _MedicalFilePageState extends State { fontSize: 12.f, fontWeight: FontWeight.w500, borderRadius: 12.r, - height: 56.h, + height: 40.h, icon: AppAssets.requests, iconColor: AppColors.primaryRedColor, iconSize: 16.w, @@ -581,7 +610,7 @@ class _MedicalFilePageState extends State { fontSize: 12.f, fontWeight: FontWeight.w500, borderRadius: 12.h, - height: 56.h, + height: 40.h, icon: AppAssets.all_medications_icon, iconColor: AppColors.primaryRedColor, iconSize: 16.h, @@ -739,7 +768,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/lab_rad_card.dart b/lib/presentation/medical_file/widgets/lab_rad_card.dart index 42f5bff2..1a56bafb 100644 --- a/lib/presentation/medical_file/widgets/lab_rad_card.dart +++ b/lib/presentation/medical_file/widgets/lab_rad_card.dart @@ -60,9 +60,7 @@ class LabRadCard extends StatelessWidget { itemCount: 3, ) : "You don't have any records yet".needTranslation.toText13( - color: AppColors.greyTextColor, - isCenter: true, - ), + color: AppColors.greyTextColor, isCenter: true), SizedBox(height: 16.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, 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 0f382dcc..037860fc 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, @@ -166,7 +167,9 @@ class MedicalFileAppointmentCard extends StatelessWidget { return DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15 ? CustomButton( text: LocaleKeys.askDoctor.tr(context: context), - onPressed: () {}, + onPressed: () { + onAskDoctorTap(); + }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, diff --git a/lib/presentation/medical_file/widgets/medical_file_card.dart b/lib/presentation/medical_file/widgets/medical_file_card.dart index 00d62c94..8c38363d 100644 --- a/lib/presentation/medical_file/widgets/medical_file_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_card.dart @@ -29,18 +29,18 @@ class MedicalFileCard extends StatelessWidget { decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: backgroundColor, borderRadius: 12.r, + hasShadow: true ), - 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 bca96814..7acd0785 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/prescriptions/prescription_delivery_order_summary_page.dart b/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart new file mode 100644 index 00000000..0bedbe23 --- /dev/null +++ b/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart @@ -0,0 +1,174 @@ +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/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/prescriptions/prescriptions_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:provider/provider.dart'; + +class PrescriptionDeliveryOrderSummaryPage extends StatelessWidget { + PrescriptionDeliveryOrderSummaryPage({super.key}); + + late PrescriptionsViewModel prescriptionsViewModel; + + @override + Widget build(BuildContext context) { + prescriptionsViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: LocaleKeys.orderSummary.tr(context: context), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.orderDetail.tr(context: context).toText16(isBold: true), + SizedBox(height: 16.h), + ...List.generate( + prescriptionsViewModel.prescriptionDetailsList.length, + (index) => Container( + margin: EdgeInsets.all(0.0), + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.all( + Radius.circular(5.r), + ), + child: Image.network( + prescriptionsViewModel.prescriptionDetailsList[index].imageSRCUrl!, + fit: BoxFit.cover, + width: 60.w, + height: 70.h, + ), + ), + Expanded( + child: Padding( + padding: EdgeInsets.all(8.h), + child: Center( + child: prescriptionsViewModel.prescriptionDetailsList[index].itemDescription!.trim().toText12(), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.deliveryLocation.tr(context: context).toText16(isBold: true), + SizedBox(height: 16.h), + ClipRRect( + clipBehavior: Clip.hardEdge, + borderRadius: BorderRadius.circular(20.r), + child: Image.network( + "https://maps.googleapis.com/maps/api/staticmap?center=${prescriptionsViewModel.locationGeocodeResponse.results.first.geometry.location.lat},${prescriptionsViewModel.locationGeocodeResponse.results.first.geometry.location.lng}&zoom=15&size=350x165&maptype=roadmap&markers=color:red%7C${prescriptionsViewModel.locationGeocodeResponse.results.first.geometry.location.lat},${prescriptionsViewModel.locationGeocodeResponse.results.first.geometry.location.lng}&key=AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng", + fit: BoxFit.contain, + ), + ), + ], + ), + ), + ), + ], + ).paddingSymmetrical(24.w, 0), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: CustomButton( + text: LocaleKeys.submit.tr(context: context), + onPressed: () async { + LoaderBottomSheet.showLoader(loadingText: "Submitting your request..."); + await prescriptionsViewModel.submitPrescriptionDeliveryRequest( + latitude: prescriptionsViewModel.locationGeocodeResponse.results.first.geometry.location.lat.toString(), + longitude: prescriptionsViewModel.locationGeocodeResponse.results.first.geometry.location.lng.toString(), + appointmentNo: prescriptionsViewModel.prescriptionDetailsList.first.appointmentNo.toString(), + dischargeID: "0", + projectID: prescriptionsViewModel.prescriptionDetailsList.first.projectID.toString(), + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getSuccessWidget(loadingText: "Request sent successfully.".needTranslation), + callBackFunc: () { + Navigator.of(context).pop(); + }, + title: "", + isCloseButtonVisible: true, + isDismissible: false, + isFullScreen: false, + ); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () { + Navigator.of(context).pop(); + }, + title: "", + isCloseButtonVisible: true, + isDismissible: false, + isFullScreen: false, + ); + }); + }, + backgroundColor: AppColors.successColor, + borderColor: AppColors.successColor.withOpacity(0.01), + textColor: AppColors.whiteColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.prescription_refill_icon, + iconColor: AppColors.whiteColor, + iconSize: 20.h, + ).paddingSymmetrical(24.h, 24.h), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/prescriptions/prescription_delivery_orders_list_page.dart b/lib/presentation/prescriptions/prescription_delivery_orders_list_page.dart new file mode 100644 index 00000000..9f2b2a2e --- /dev/null +++ b/lib/presentation/prescriptions/prescription_delivery_orders_list_page.dart @@ -0,0 +1,111 @@ +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/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/prescriptions/prescriptions_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/history/widget/RequestStatus.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_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/chip/app_custom_chip_widget.dart'; +import 'package:provider/provider.dart'; + +class PrescriptionDeliveryOrdersListPage extends StatelessWidget { + const PrescriptionDeliveryOrdersListPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: LocaleKeys.orders.tr(context: context), + child: SingleChildScrollView( + child: Consumer(builder: (context, model, child) { + return Column( + children: [ + ListView.builder( + itemCount: model.isPrescriptionsDeliveryOrdersLoading + ? 4 + : model.prescriptionsOrderList.isNotEmpty + ? model.prescriptionsOrderList.length + : 1, + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + padding: const EdgeInsets.only(left: 0, right: 8), + itemBuilder: (context, index) { + return model.isPrescriptionsDeliveryOrdersLoading + ? LabResultItemView( + onTap: () {}, + labOrder: null, + index: index, + isLoading: true, + ) + : model.prescriptionsOrderList.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: () { + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.h, + children: [ + RequestStatus(status: model.prescriptionsOrderList[index].statusId ?? 0), + "Req ID: ${model.prescriptionsOrderList[index].iD}".toText16(color: AppColors.textColor, weight: FontWeight.w600), + Row( + spacing: 4.w, + children: [ + chip(Utils.getDayMonthYearDateFormatted(DateTime.tryParse(model.prescriptionsOrderList[index].created!)), AppAssets.calendar, + AppColors.blackBgColor), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ), + ), + ) + : Utils.getNoDataWidget(context, noDataText: "You don't have any prescription orders yet.".needTranslation); + }, + ).paddingSymmetrical(24.h, 0.h), + ], + ); + }), + ), + ), + ); + } + + chip(String title, String iconString, Color iconColor) { + return AppCustomChipWidget( + labelText: title, + icon: iconString, + iconColor: iconColor, + iconSize: 12.h, + ); + } +} diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index 473f79af..d5e8138a 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -13,23 +13,22 @@ 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'; class PrescriptionDetailPage extends StatefulWidget { - PrescriptionDetailPage({super.key, required this.prescriptionsResponseModel}); + PrescriptionDetailPage({super.key, required this.prescriptionsResponseModel, required this.isFromAppointments}); PatientPrescriptionsResponseModel prescriptionsResponseModel; + bool isFromAppointments = false; @override State createState() => _PrescriptionDetailPageState(); @@ -45,10 +44,12 @@ class _PrescriptionDetailPageState extends State { checkAndRemove(false); // locationUtils = new LocationUtils(isShowConfirmDialog: true, context: context); // WidgetsBinding.instance.addPostFrameCallback((_) => locationUtils.getCurrentLocation()); - scheduleMicrotask(() { - prescriptionsViewModel.setPrescriptionsDetailsLoading(); - prescriptionsViewModel.getPrescriptionDetails(widget.prescriptionsResponseModel); - }); + if (!widget.isFromAppointments) { + scheduleMicrotask(() { + prescriptionsViewModel.setPrescriptionsDetailsLoading(); + prescriptionsViewModel.getPrescriptionDetails(widget.prescriptionsResponseModel); + }); + } super.initState(); } @@ -127,7 +128,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( @@ -147,7 +149,7 @@ class _PrescriptionDetailPageState extends State { CustomButton( text: "Download Prescription".needTranslation, onPressed: () async { - LoaderBottomSheet.showLoader(); + LoaderBottomSheet.showLoader(loadingText: "Fetching prescription PDF, Please wait...".needTranslation); await prescriptionVM.getPrescriptionPDFBase64(widget.prescriptionsResponseModel).then((val) async { LoaderBottomSheet.hideLoader(); if (prescriptionVM.prescriptionPDFBase64Data.isNotEmpty) { @@ -182,8 +184,10 @@ class _PrescriptionDetailPageState extends State { ), ), ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), ListView.builder( shrinkWrap: true, + padding: EdgeInsets.zero, physics: NeverScrollableScrollPhysics(), itemCount: prescriptionVM.isPrescriptionsDetailsLoading ? 5 : prescriptionVM.prescriptionDetailsList.length, itemBuilder: (context, index) { @@ -214,18 +218,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/prescriptions/prescriptions_list_page.dart b/lib/presentation/prescriptions/prescriptions_list_page.dart index 1293c9c5..3d631ce7 100644 --- a/lib/presentation/prescriptions/prescriptions_list_page.dart +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -6,6 +6,7 @@ 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/location_util.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'; @@ -14,12 +15,15 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.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/presentation/lab/lab_result_item_view.dart'; +import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_delivery_orders_list_page.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.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/loader/bottomsheet_loader.dart'; +import 'package:hmg_patient_app_new/widgets/map/map_utility_screen.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:hmg_patient_app_new/widgets/shimmer/common_shimmer_widget.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; @@ -51,6 +55,14 @@ class _PrescriptionsListPageState extends State { backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.prescriptions.tr(context: context), + requests: () { + prescriptionsViewModel.getPrescriptionOrdersList(); + Navigator.of(context).push( + CustomPageRoute( + page: PrescriptionDeliveryOrdersListPage(), + ), + ); + }, child: SingleChildScrollView( child: Consumer(builder: (context, model, child) { return Column( @@ -237,16 +249,26 @@ class _PrescriptionsListPageState extends State { text: prescription.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context), - onPressed: () {}, - backgroundColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor.withOpacity(0.15) : AppColors.greyF7Color, - borderColor: AppColors.successColor.withOpacity(0.01), - textColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), - fontSize: prescription.isHomeMedicineDeliverySupported! ? 14 : 12, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - icon: AppAssets.prescription_refill_icon, + onPressed: () async { + if (prescription.isHomeMedicineDeliverySupported!) { + LoaderBottomSheet.showLoader(loadingText: "Fetching prescription details...".needTranslation); + await prescriptionsViewModel.getPrescriptionDetails(prescriptionsViewModel.patientPrescriptionOrders[index], + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + prescriptionsViewModel.initiatePrescriptionDelivery(); + }); + } + }, + backgroundColor: + prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor.withOpacity(0.15) : AppColors.greyF7Color, + borderColor: AppColors.successColor.withOpacity(0.01), + textColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), + fontSize: prescription.isHomeMedicineDeliverySupported! ? 14 : 12, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.prescription_refill_icon, iconColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), iconSize: 14.h, ), @@ -255,22 +277,20 @@ class _PrescriptionsListPageState extends State { Expanded( flex: 1, child: Container( - height: 40.h, - width: 40.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + height: 40.h, + width: 40.w, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.textColor, - borderRadius: 10.h, - ), + borderRadius: 12, + ), child: Padding( - padding: EdgeInsets.all(8.h), - child: Transform.flip( + padding: EdgeInsets.all(12.h), + child: Transform.flip( flipX: appState.isArabic(), child: Utils.buildSvgWithAssets( icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.whiteColor, - width: 10.h, - height: 10.h, - fit: BoxFit.contain, + fit: BoxFit.contain, ), ), ), @@ -278,9 +298,12 @@ class _PrescriptionsListPageState extends State { model.setPrescriptionsDetailsLoading(); Navigator.of(context).push( CustomPageRoute( - page: PrescriptionDetailPage(prescriptionsResponseModel: prescription), - ), - ); + page: PrescriptionDetailPage( + prescriptionsResponseModel: prescription, + isFromAppointments: false, + ), + ), + ); }), ), ], diff --git a/lib/presentation/radiology/radiology_orders_page.dart b/lib/presentation/radiology/radiology_orders_page.dart index 6662a8ed..cb925ef9 100644 --- a/lib/presentation/radiology/radiology_orders_page.dart +++ b/lib/presentation/radiology/radiology_orders_page.dart @@ -9,16 +9,16 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; -import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; -import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/radiology/radiology_result_page.dart'; +import 'package:hmg_patient_app_new/presentation/radiology/search_radiology.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'; @@ -32,13 +32,13 @@ class RadiologyOrdersPage extends StatefulWidget { class _RadiologyOrdersPageState extends State { late RadiologyViewModel radiologyViewModel; - + String selectedFilterText = ''; int? expandedIndex; @override void initState() { scheduleMicrotask(() { - radiologyViewModel.initRadiologyProvider(); + radiologyViewModel.initRadiologyViewModel(); }); super.initState(); } @@ -50,6 +50,24 @@ class _RadiologyOrdersPageState extends State { backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.radiology.tr(context: context), + search: () async { + final lavVM = Provider.of(context, listen: false); + if (lavVM.isLabOrdersLoading) { + return; + } else { + String? value = await Navigator.of(context).push( + CustomPageRoute( + page: SearchRadiologyContent(radiologySuggestionsList: radiologyViewModel.radiologySuggestions), + fullScreenDialog: true, + direction: AxisDirection.down, + ), + ); + if (value != null) { + selectedFilterText = value; + radiologyViewModel.filterRadiologyReports(value); + } + } + }, child: SingleChildScrollView( child: Consumer( builder: (context, model, child) { @@ -58,7 +76,25 @@ class _RadiologyOrdersPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Expandable list + selectedFilterText!.isNotEmpty + ? AppCustomChipWidget( + padding: EdgeInsets.symmetric(horizontal: 5.h), + labelText: selectedFilterText!, + deleteIcon: 'assets/images/svg/cross_circle.svg', + backgroundColor: AppColors.alertColor, + textColor: AppColors.whiteColor, + deleteIconColor: AppColors.whiteColor, + deleteIconHasColor: true, + onDeleteTap: () { + setState(() { + selectedFilterText = ''; + model.filterRadiologyReports(''); + }); + }, + // chipType: ChipTypeEnum.alert, + // isSelected: true, + ) + : SizedBox(), ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), @@ -78,127 +114,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/radiology/search_radiology.dart b/lib/presentation/radiology/search_radiology.dart new file mode 100644 index 00000000..98f5c901 --- /dev/null +++ b/lib/presentation/radiology/search_radiology.dart @@ -0,0 +1,154 @@ +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_export.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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/input_widget.dart'; +import 'package:sizer/sizer.dart'; + +class SearchRadiologyContent extends StatefulWidget { + final List radiologySuggestionsList; + + const SearchRadiologyContent({super.key, required this.radiologySuggestionsList}); + + @override + State createState() => _SearchRadiologyContentContentState(); +} + +class _SearchRadiologyContentContentState extends State { + TextEditingController searchEditingController = TextEditingController(); + List filteredSuggestions = []; + + @override + void initState() { + super.initState(); + filteredSuggestions = List.from(widget.radiologySuggestionsList); + + // Listen for changes in the search field + searchEditingController.addListener(() { + filterSuggestions(); + }); + } + + @override + void dispose() { + searchEditingController.dispose(); + super.dispose(); + } + + void filterSuggestions() { + final query = searchEditingController.text.toLowerCase(); + + if (query.isEmpty) { + setState(() { + filteredSuggestions = List.from(widget.radiologySuggestionsList); + }); + } else { + setState(() { + filteredSuggestions = widget.radiologySuggestionsList.where((suggestion) => suggestion.toLowerCase().contains(query)).toList(); + }); + } + } + + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: LocaleKeys.radiology.tr(), + isClose: true, + bottomChild: Container( + color: Colors.white, + padding: EdgeInsets.all(ResponsiveExtension(20).h), + child: CustomButton( + text: LocaleKeys.search.tr(), + icon: AppAssets.search_icon, + iconColor: Colors.white, + onPressed: () => Navigator.pop(context, searchEditingController.text), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 24, right: 24, top: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextInputWidget( + labelText: "Search radiology results", + hintText: "Type test description", + controller: searchEditingController, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(9).h, + horizontal: ResponsiveExtension(15).h, + ), + ), + SizedBox(height: ResponsiveExtension(20).h), + if (filteredSuggestions.isNotEmpty) ...[ + "Suggestions".toText16(isBold: true), + ], + ], + ), + ), + SingleChildScrollView( + physics: NeverScrollableScrollPhysics(), + padding: const EdgeInsets.only(left: 24, right: 24, bottom: 24, top: 16), + child: Wrap( + alignment: WrapAlignment.start, + spacing: 10, + runSpacing: 10, + children: filteredSuggestions + .map((label) => SuggestionChip( + label: label, + onTap: () { + searchEditingController.text = label; + }, + )) + .toList(), + ), + ), + ], + ), + ); + } +} + +class SuggestionChip extends StatelessWidget { + final String label; + final bool isSelected; + final VoidCallback? onTap; + + const SuggestionChip({ + super.key, + required this.label, + this.isSelected = false, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + decoration: BoxDecoration( + color: isSelected ? AppColors.primaryRedColor : AppColors.whiteColor, + borderRadius: BorderRadius.circular(8), + ), + child: label.toText12( + color: isSelected ? Colors.white : Colors.black87, + fontWeight: FontWeight.w500, + ), + ), + ); + } +} diff --git a/lib/presentation/services/services_page.dart b/lib/presentation/services/services_page.dart deleted file mode 100644 index 24a259b7..00000000 --- a/lib/presentation/services/services_page.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/theme/colors.dart'; - -class ServicesPage extends StatelessWidget { - const ServicesPage({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColors.bgScaffoldColor, - appBar: AppBar( - title: const Text('Appointments'), - backgroundColor: AppColors.bgScaffoldColor, - ), - body: const Center( - child: Text( - 'Appointments Page', - style: TextStyle(fontSize: 24), - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/presentation/todo/todo_page.dart b/lib/presentation/todo/todo_page.dart deleted file mode 100644 index 20f8cd41..00000000 --- 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 00000000..f9995dca --- /dev/null +++ b/lib/presentation/todo_section/ancillary_order_payment_page.dart @@ -0,0 +1,644 @@ +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; + final List selectedProcedures; + final double totalAmount; + + const AncillaryOrderPaymentPage({ + super.key, + required this.appointmentDate, + 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) { + _startApplePay(); + } + }) + : 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: 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 new file mode 100644 index 00000000..449d21e1 --- /dev/null +++ b/lib/presentation/todo_section/ancillary_procedures_details_page.dart @@ -0,0 +1,656 @@ +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 AncillaryOrderDetailsList extends StatefulWidget { + final int appointmentNoVida; + final int orderNo; + final int projectID; + final String projectName; + + const AncillaryOrderDetailsList({ + super.key, + required this.appointmentNoVida, + required this.orderNo, + required this.projectID, + required this.projectName, + }); + + @override + State createState() => _AncillaryOrderDetailsListState(); +} + +class _AncillaryOrderDetailsListState 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 true; + 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 (widget.projectName.isNotEmpty) + AppCustomChipWidget( + labelText: widget.projectName, + ), + 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: [ + Utils.getPaymentAmountWithSymbol( + _getTotalAmount().toStringAsFixed(2).toText14( + isBold: true, + weight: FontWeight.bold, + color: AppColors.primaryRedColor, + ), + AppColors.textColorLight, + 13, + isSaudiCurrency: true, + ), + // + // _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 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: , + ), + // 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: [ + Utils.getPaymentAmountWithSymbol( + (procedure.patientShare ?? 0).toStringAsFixed(2).toText13(weight: FontWeight.w600), + AppColors.textColorLight, + 13, + isSaudiCurrency: true, + ), + ], + ), + ], + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "VAT (15%)".needTranslation.toText10(color: AppColors.textColorLight), + SizedBox(height: 4.h), + Row( + children: [ + Utils.getPaymentAmountWithSymbol( + (procedure.patientTaxAmount ?? 0).toStringAsFixed(2).toText13(weight: FontWeight.w600), + AppColors.textColorLight, + 13, + isSaudiCurrency: true, + ), + ], + ), + ], + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Total".needTranslation.toText10(color: AppColors.textColorLight), + SizedBox(height: 4.h), + Row( + children: [ + Utils.getPaymentAmountWithSymbol( + (procedure.patientShareWithTax ?? 0).toStringAsFixed(2).toText13(weight: FontWeight.w600), + AppColors.textColorLight, + 13, + isSaudiCurrency: true, + ), + ], + ), + ], + ), + ), + ], + ), + ], + ), + ), + ), + ), + ), + ), + )); + } + + 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(), + appointmentDate: orderData.appointmentDate, + ), + ), + ); + }, + isDisabled: !isButtonEnabled, + textColor: AppColors.whiteColor, + borderRadius: 12.r, + borderColor: Colors.transparent, + 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 00000000..11e258fa --- /dev/null +++ b/lib/presentation/todo_section/todo_page.dart @@ -0,0 +1,96 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/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 { + + late AppState appState; + + @override + void initState() { + final TodoSectionViewModel todoSectionViewModel = context.read(); + scheduleMicrotask(() async { + if (appState.isAuthenticated) { + 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) { + appState = getIt.get(); + 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: AncillaryOrderDetailsList( + appointmentNoVida: order.appointmentNo ?? 0, + orderNo: order.orderNo ?? 0, + projectID: order.projectID ?? 0, + projectName: order.projectName ?? "", + ))); + 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 00000000..31a778f0 --- /dev/null +++ b/lib/presentation/todo_section/widgets/ancillary_orders_list.dart @@ -0,0 +1,284 @@ +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.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, + 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), + ], + ), + ), + ], + ), + + SizedBox(height: 12.h), + + // Chips for Appointment Info and Status + Wrap( + direction: Axis.horizontal, + 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( + 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, + 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 00000000..ba2f94d9 --- /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/routes/app_routes.dart b/lib/routes/app_routes.dart index 5a932161..a0ee1e59 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -2,8 +2,10 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; import 'package:hmg_patient_app_new/presentation/authentication/register.dart'; import 'package:hmg_patient_app_new/presentation/authentication/register_step2.dart'; -import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; +import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/comprehensive_checkup_page.dart'; +import 'package:hmg_patient_app_new/presentation/e_referral/e_referral_page_home.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; +import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/splashPage.dart'; @@ -14,6 +16,9 @@ class AppRoutes { static const String registerStepTwo = '/registerStepTwo'; static const String landingScreen = '/landingScreen'; static const String medicalFilePage = '/medicalFilePage'; + static const String eReferralPage = '/erReferralPage'; + static const String comprehensiveCheckupPage = '/comprehensiveCheckupPage'; + static const String homeHealthCarePage = '/homeHealthCarePage'; static Map get routes => { initialRoute: (context) => SplashPage(), @@ -21,6 +26,9 @@ class AppRoutes { landingScreen: (context) => LandingNavigation(), register: (context) => RegisterNew(), registerStepTwo: (context) => RegisterNewStep2(), - medicalFilePage: (context) => MedicalFilePage(), + medicalFilePage: (context) => MedicalFilePage(), + eReferralPage: (context) => EReferralPage(), + comprehensiveCheckupPage: (context) => ComprehensiveCheckupPage(), + homeHealthCarePage: (context) => HhcProceduresPage() }; } diff --git a/lib/services/analytics/flows/app_nav.dart b/lib/services/analytics/flows/app_nav.dart index bd9186c9..75570c69 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 c1c874b2..1cc76442 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 8a015d8a..986baa4f 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 aeefefb0..bda17275 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 9df66332..617539aa 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/services/navigation_service.dart b/lib/services/navigation_service.dart index cb4405cb..fe951400 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -13,8 +13,8 @@ class NavigationService { return navigatorKey.currentState!.push(route); } - Future pushAndRemoveUntil(Route route,RoutePredicate predicate) { - return navigatorKey.currentState!.pushAndRemoveUntil(route,predicate); + Future pushAndRemoveUntil(Route route, RoutePredicate predicate) { + return navigatorKey.currentState!.pushAndRemoveUntil(route, predicate); } void pop([T? result]) { @@ -38,11 +38,22 @@ class NavigationService { navigatorKey.currentState?.pushReplacementNamed(routeName); } + void pushPageRoute(String routeName) { + navigatorKey.currentState?.pushNamed(routeName); + } - - Future pushToOtpScreen({required String phoneNumber, required Function(int code) checkActivationCode, required Function(String phoneNumber) onResendOTPPressed, bool isFormFamilyFile = false}) { + Future pushToOtpScreen( + {required String phoneNumber, + required Function(int code) checkActivationCode, + required Function(String phoneNumber) onResendOTPPressed, + bool isFormFamilyFile = false}) { return navigatorKey.currentState!.push( - MaterialPageRoute(builder: (_) => OTPVerificationScreen(phoneNumber: phoneNumber, checkActivationCode: checkActivationCode, onResendOTPPressed: onResendOTPPressed, isFormFamilyFile : isFormFamilyFile)), + MaterialPageRoute( + builder: (_) => OTPVerificationScreen( + phoneNumber: phoneNumber, + checkActivationCode: checkActivationCode, + onResendOTPPressed: onResendOTPPressed, + isFormFamilyFile: isFormFamilyFile)), ); } diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 6ccf8b16..b3abe6ab 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -3,21 +3,12 @@ import 'package:flutter/material.dart'; class AppColors { static const transparent = Colors.transparent; static const mainPurple = Color(0xFF7954F7); - static const purpleBg = Color(0xFFAEA4FC); - static const deepPurple = Color(0xFF7C65E7); - static const logoColor = Color(0xFF7C65E7); - static const buttonColor = Color(0xFF6A46F5); - static const splashBgColor = Color(0xFF3C355D); - static const lightGray = Color(0xFFF4F5F7); - static const lightPurple = Color(0xFFB7A3E6); static const scaffoldBgColor = Color(0xFFF8F8F8); static const bottomSheetBgColor = Color(0xFFF8F8FA); static const lightGreyEFColor = Color(0xffeaeaff); static const greyF7Color = Color(0xffF7F7F7); static const lightGrayColor = Color(0xff808080); - static const buttonGrayColor = Color(0xffF1F1F1); - static const lightPurpleAlpha = Color(0x5AB7A3E6); // New UI Colors static const whiteColor = Color(0xFFffffff); @@ -37,11 +28,10 @@ class AppColors { static const Color warningColorYellow = Color(0xFFF4A308); static const Color blackBgColor = Color(0xFF2E3039); static const blackColor = textColor; - static const Color inputLabelTextColor = Color(0xff898A8D); + static const Color inputLabelTextColor = Color(0xff898A8D); static const Color greyTextColor = Color(0xFF8F9AA3); static const Color lightGrayBGColor = Color(0x142E3039); - static const lightGreenColor = Color(0xFF0ccedde); static const textGreenColor = Color(0xFF18C273); static const Color ratingColorYellow = Color(0xFFFFAF15); @@ -57,27 +47,38 @@ class AppColors { static const Color chipPrimaryRedBorderColor = Color(0xFFED1C2B); static const Color chipSecondaryLightRedColor = Color(0xFFFEE9EA); -static const Color successLightColor = Color(0xFF18C273); -static const Color errorLightColor = Color(0xFFED1C2B); -static const Color alertLightColor = Color(0xFFD48D05); -static const Color infoLightColor = Color(0xFF0B85F7); -static const Color warningLightColor = Color(0xFFFFCC00); -static const Color greyLightColor = Color(0xFFEFEFF0); -static const Color thumbColor = Color(0xFF18C273); -static const Color switchBackgroundColor = Color(0x2618C273); + static const Color successLightColor = Color(0xFF18C273); + static const Color errorLightColor = Color(0xFFED1C2B); + static const Color alertLightColor = Color(0xFFD48D05); + static const Color infoLightColor = Color(0xFF0B85F7); + static const Color warningLightColor = Color(0xFFFFCC00); + static const Color greyLightColor = Color(0xFFEFEFF0); + static const Color thumbColor = Color(0xFF18C273); + static const Color switchBackgroundColor = Color(0x2618C273); + + static const Color bottomNAVBorder = Color(0xFFEEEEEE); -static const Color bottomNAVBorder = Color(0xFFEEEEEE); + static const Color quickLoginColor = Color(0xFF666666); -static const Color quickLoginColor = Color(0xFF666666); + static const Color tooltipTextColor = Color(0xFF414D55); + static const Color graphGridColor = Color(0x4D18C273); + static const Color criticalLowAndHigh = Color(0xFFED1C2B); + static const Color highAndLow = Color(0xFFFFAF15); + static const Color labelTextColor = Color(0xFF838383); + static const Color calenderTextColor = Color(0xFFD0D0D0); + static const Color lightGreenButtonColor = Color(0x2618C273); -static const Color tooltipTextColor = Color(0xFF414D55); -static const Color graphGridColor = Color(0x4D18C273); -static const Color criticalLowAndHigh = Color(0xFFED1C2B); -static const Color highAndLow = Color(0xFFFFAF15); -static const Color labelTextColor = Color(0xFF838383); -static const Color calenderTextColor = Color(0xFFD0D0D0); -static const Color lightGreenButtonColor = Color(0x2618C273); + static const Color lightRedButtonColor = Color(0x1AED1C2B); -static const Color lightRedButtonColor = Color(0x1AED1C2B); + // Status Colors + static const Color statusPendingColor = Color(0xffCC9B14); + static const Color statusProcessingColor = Color(0xff2E303A); + static const Color statusCompletedColor = Color(0xff359846); + static const Color statusRejectedColor = Color(0xffD02127); + // Info Banner Colors + static const Color infoBannerBgColor = Color(0xFFFFF4E6); + static const Color infoBannerBorderColor = Color(0xFFFFE5B4); + static const Color infoBannerIconColor = Color(0xFFCC9B14); + static const Color infoBannerTextColor = Color(0xFF856404); } diff --git a/lib/widgets/CustomSwitch.dart b/lib/widgets/CustomSwitch.dart index 784e446c..bca4e69c 100644 --- a/lib/widgets/CustomSwitch.dart +++ b/lib/widgets/CustomSwitch.dart @@ -21,7 +21,7 @@ class _CustomSwitchState extends State { width: 48.w, height: 30.h, decoration: BoxDecoration( - color: AppColors.switchBackgroundColor , + color: widget.value ? AppColors.switchBackgroundColor : AppColors.greyTextColor, borderRadius: BorderRadius.circular(18), ), child: AnimatedAlign( @@ -32,7 +32,7 @@ class _CustomSwitchState extends State { width: 28.w, height: 28.h, decoration: BoxDecoration( - color: AppColors.thumbColor, + color: widget.value? AppColors.thumbColor : AppColors.greyColor, shape: BoxShape.circle, ), ), diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index 8e07631c..734fe0f6 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -54,7 +54,7 @@ class CollapsingListView extends StatelessWidget { SliverAppBar( automaticallyImplyLeading: false, pinned: true, - expandedHeight: MediaQuery.of(context).size.height * 0.12.h, + expandedHeight: MediaQuery.of(context).size.height * 0.11.h, stretch: true, systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Brightness.light), surfaceTintColor: Colors.transparent, @@ -92,8 +92,7 @@ class CollapsingListView extends StatelessWidget { t, )!, child: Padding( - padding: EdgeInsets.only( - left: appState.isArabic() ? 0 : leftPadding, right: appState.isArabic() ? leftPadding : 0, bottom: bottomPadding), + padding: EdgeInsets.only(left: appState.isArabic() ? 0 : leftPadding, right: appState.isArabic() ? leftPadding : 0, bottom: bottomPadding), child: Row( spacing: 4.h, children: [ @@ -110,18 +109,11 @@ class CollapsingListView extends StatelessWidget { color: AppColors.blackColor, letterSpacing: -0.5), ).expanded, - if (logout != null) - actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(logout!), - if (report != null) - actionButton(context, t, title: "Report".needTranslation, icon: AppAssets.report_icon).onPress(report!), - if (history != null) - actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon) - .onPress(history!), - if (instructions != null) - actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(instructions!), - if (requests != null) - actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon) - .onPress(requests!), + if (logout != null) actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(logout!), + if (report != null) actionButton(context, t, title: "Feedback".needTranslation, icon: AppAssets.report_icon).onPress(report!), + if (history != null) actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon).onPress(history!), + if (instructions != null) actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(instructions!), + if (requests != null) actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon).onPress(requests!), if (search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(search!).paddingOnly(right: 24), if (trailing != null) trailing!, ], @@ -170,7 +162,7 @@ class CollapsingListView extends StatelessWidget { style: context.dynamicTextStyle( color: AppColors.primaryRedColor, letterSpacing: -0.4, - fontSize: (14 - (2 * (1 - t))).f, + fontSize: (12 - (2 * (1 - t))).f, fontWeight: FontWeight.lerp( FontWeight.w300, FontWeight.w500, diff --git a/lib/widgets/buttons/custom_button.dart b/lib/widgets/buttons/custom_button.dart index f236bd97..b823eae9 100644 --- a/lib/widgets/buttons/custom_button.dart +++ b/lib/widgets/buttons/custom_button.dart @@ -64,17 +64,17 @@ 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)), + side: borderSide ?? BorderSide(width: borderWidth.h, color: borderColor)), child: Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, 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, ), diff --git a/lib/widgets/buttons/default_button.dart b/lib/widgets/buttons/default_button.dart index eb02c38f..d8d8cacd 100644 --- a/lib/widgets/buttons/default_button.dart +++ b/lib/widgets/buttons/default_button.dart @@ -1,7 +1,6 @@ -import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; extension WithContainer on Widget { Widget get insideContainer => Container( @@ -26,20 +25,22 @@ class DefaultButton extends StatelessWidget { final double height; final double borderRadius; - const DefaultButton(this.text, this.onPress, - {Key? key, - this.color, - this.isTextExpanded = true, - this.svgIcon, - this.disabledColor, - this.count = 0, - this.textColor = Colors.white, - this.iconData, - this.fontSize, - this.colors, - this.height = 50, - this.borderRadius = 100}) - : super(key: key); + const DefaultButton( + this.text, + this.onPress, { + super.key, + this.color, + this.isTextExpanded = true, + this.svgIcon, + this.disabledColor, + this.count = 0, + this.textColor = Colors.white, + this.iconData, + this.fontSize, + this.colors, + this.height = 50, + this.borderRadius = 100, + }); @override Widget build(BuildContext context) { @@ -48,30 +49,20 @@ class DefaultButton extends StatelessWidget { child: Container( height: height, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(borderRadius), - gradient: onPress == null - ? LinearGradient( - colors: [ - disabledColor ?? const Color(0xffEAEAEA), - disabledColor ?? const Color(0xffEAEAEA), - ], - ) - : LinearGradient( - transform: const GradientRotation(.83), - begin: Alignment.topRight, - end: Alignment.bottomLeft, - colors: colors ?? - [ - AppColors.buttonColor, - AppColors.buttonColor, - ], - ), - ), + borderRadius: BorderRadius.circular(borderRadius), + gradient: onPress == null + ? LinearGradient( + colors: [ + disabledColor ?? const Color(0xffEAEAEA), + disabledColor ?? const Color(0xffEAEAEA), + ], + ) + : null), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ if (iconData != null) Icon(iconData, color: textColor), - if (svgIcon != null) SvgPicture.asset(svgIcon ?? "", color: textColor), + if (svgIcon != null) SvgPicture.asset(svgIcon ?? ""), if (!isTextExpanded) Padding( padding: EdgeInsets.only( diff --git a/lib/widgets/chip/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart index f090f4fa..6904edc5 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -24,6 +24,7 @@ class AppCustomChipWidget extends StatelessWidget { this.padding = EdgeInsets.zero, this.onChipTap, this.labelPadding, + this.onDeleteTap, }); final String? labelText; @@ -42,77 +43,82 @@ class AppCustomChipWidget extends StatelessWidget { final EdgeInsets? padding; final EdgeInsetsDirectional? labelPadding; final void Function()? onChipTap; + final void Function()? onDeleteTap; @override Widget build(BuildContext context) { final iconS = iconSize ?? 12.w; return GestureDetector( onTap: onChipTap, - child: ChipTheme( - data: ChipThemeData( - padding: EdgeInsets.zero, - shape: SmoothRectangleBorder( - side: BorderSide( - width: 10.w, - color: Colors.transparent, // Crucially, set color to transparent - style: BorderStyle.none, + child: SizedBox( + child: ChipTheme( + data: ChipThemeData( + padding: EdgeInsets.zero, + shape: SmoothRectangleBorder( + side: BorderSide( + width: 10.w, + color: Colors.transparent, // Crucially, set color to transparent + style: BorderStyle.none, + ), + borderRadius: BorderRadius.circular(8.r), // Apply a border radius of 16.0 ), - borderRadius: BorderRadius.circular(isFoldable || isTablet ? 6.r : 8.r), ), - ), - child: icon.isNotEmpty - ? Chip( - avatar: icon.isNotEmpty - ? Padding( - padding: EdgeInsets.only(left: 8.w, right: 6.w), - child: Utils.buildSvgWithAssets( + child: icon.isNotEmpty + ? Chip( + avatar: icon.isNotEmpty + ? Utils.buildSvgWithAssets( icon: icon, width: iconS, height: iconS, iconColor: iconHasColor ? iconColor : null, fit: BoxFit.contain, - ), - ) - : SizedBox.shrink(), - avatarBoxConstraints: BoxConstraints(), - label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor), - padding: EdgeInsets.zero, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - labelPadding: EdgeInsetsDirectional.only(end: 8.w), - backgroundColor: backgroundColor, - shape: shape ?? - SmoothRectangleBorder( - borderRadius: BorderRadius.circular(8.r), - smoothness: 10, - side: BorderSide(color: AppColors.transparent, width: 1.5), - ), - deleteIcon: deleteIcon?.isNotEmpty == true - ? Utils.buildSvgWithAssets( - icon: deleteIcon!, - width: iconS, - height: iconS, - iconColor: deleteIconHasColor ? deleteIconColor : null, - ) - : null, - onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, - ) - : Chip( - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor), - padding: EdgeInsets.zero, - backgroundColor: backgroundColor, - shape: shape ?? - SmoothRectangleBorder( - borderRadius: BorderRadius.circular(8.r), - smoothness: 10, - side: BorderSide(color: AppColors.transparent, width: 1.5), - ), - labelPadding: EdgeInsetsDirectional.only(start: 8.w, end: 8.w), - deleteIcon: deleteIcon?.isNotEmpty == true - ? Utils.buildSvgWithAssets(icon: deleteIcon!, width: iconS, height: iconS, iconColor: deleteIconHasColor ? deleteIconColor : null) - : null, - onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, - ), + ) + : SizedBox.shrink(), + label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor), + padding: padding, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + labelPadding: labelPadding ?? EdgeInsetsDirectional.only(end: deleteIcon?.isNotEmpty == true ? 2.w : 8.w), + backgroundColor: backgroundColor, + shape: shape ?? + SmoothRectangleBorder( + borderRadius: BorderRadius.circular(8.r), + smoothness: 10, + side: BorderSide(color: AppColors.transparent, width: 1.5), + ), + deleteIcon: deleteIcon?.isNotEmpty == true + ? InkWell( + onTap: onDeleteTap, + child: Utils.buildSvgWithAssets( + icon: deleteIcon!, + width: iconS, + height: iconS, + iconColor: deleteIconHasColor ? deleteIconColor : null, + ), + ) + : null, + onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, + ) + : Chip( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor, isCenter: true), + padding: EdgeInsets.zero, + backgroundColor: backgroundColor, + shape: shape ?? + SmoothRectangleBorder( + borderRadius: BorderRadius.circular(8.r), + smoothness: 10, + side: BorderSide(color: AppColors.transparent, width: 1.5), + ), + labelPadding: labelPadding ?? EdgeInsetsDirectional.only(start: 6.w, end: deleteIcon?.isNotEmpty == true ? 2.w : 8.w), + deleteIcon: deleteIcon?.isNotEmpty == true + ? InkWell( + onTap: onDeleteTap, + child: Utils.buildSvgWithAssets( + icon: deleteIcon!, width: iconS, height: iconS, iconColor: deleteIconHasColor ? deleteIconColor : null)) + : null, + onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, + ), + ), ), ); } diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index 3aaefe75..99ff2307 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -105,18 +105,19 @@ 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, + EdgeInsets? padding, Color backgroundColor = AppColors.bottomSheetBgColor, - VoidCallback? onCloseClicked + VoidCallback? onCloseClicked, }) { showModalBottomSheet( sheetAnimationStyle: AnimationStyle( @@ -124,7 +125,8 @@ void showCommonBottomSheetWithoutHeight( reverseDuration: Duration(milliseconds: 300), ), constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width, // Full width + + maxWidth: MediaQuery.sizeOf(context).width//MediaQuery.of(context).size.width, // Full width ), context: context, isScrollControlled: true, @@ -146,7 +148,7 @@ void showCommonBottomSheetWithoutHeight( physics: ClampingScrollPhysics(), child: isCloseButtonVisible ? Container( - padding: EdgeInsets.only( + padding: padding ?? const EdgeInsets.only( left: 24, top: 24, right: 24, @@ -158,25 +160,30 @@ void showCommonBottomSheetWithoutHeight( ), child: Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - titleWidget ?? - Expanded( - child: title.toText20(weight: FontWeight.w600), - ), - Utils.buildSvgWithAssets( - icon: AppAssets.close_bottom_sheet_icon, - iconColor: Color(0xff2B353E), - ).onPress(() { - onCloseClicked?.call(); - Navigator.of(context).pop(); - }), - ], + Padding( + padding: padding != null? EdgeInsets.symmetric(horizontal: 24.w): EdgeInsets.zero, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + titleWidget ?? + Expanded( + child: title.toText20(weight: FontWeight.w600), + ), + if (isCloseButtonVisible) ...[ + Utils.buildSvgWithAssets( + icon: AppAssets.close_bottom_sheet_icon, + iconColor: Color(0xff2B353E), + ).onPress(() { + onCloseClicked?.call(); + Navigator.of(context).pop(); + }),], + ], + ), ), - SizedBox(height: 16.h), + isCloseButtonVisible ? SizedBox(height: 16.h) : SizedBox.shrink(), child, ], ), @@ -187,7 +194,9 @@ void showCommonBottomSheetWithoutHeight( ); }, ).then((value) { - callBackFunc(); + if (callBackFunc != null) { + callBackFunc(); + } }); } diff --git a/lib/widgets/dialogs/confirm_dialog.dart b/lib/widgets/dialogs/confirm_dialog.dart index f753681c..ce597c2d 100644 --- a/lib/widgets/dialogs/confirm_dialog.dart +++ b/lib/widgets/dialogs/confirm_dialog.dart @@ -15,7 +15,7 @@ class ConfirmDialog extends StatelessWidget { final VoidCallback? onTap; final VoidCallback? onCloseTap; - const ConfirmDialog({Key? key, this.title, required this.message, this.okTitle, this.onTap, this.onCloseTap}) : super(key: key); + const ConfirmDialog({super.key, this.title, required this.message, this.okTitle, this.onTap, this.onCloseTap}); @override Widget build(BuildContext context) { diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index d943731c..4b3f091a 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -43,6 +43,11 @@ class TextInputWidget extends StatelessWidget { final Color? labelColor; final Function(String)? onSubmitted; + // new multiline options + final bool isMultiline; + final int minLines; + final int maxLines; + // final List countryList; // final Function(Country)? onCountryChange; @@ -73,10 +78,14 @@ class TextInputWidget extends StatelessWidget { this.isWalletAmountInput = false, this.suffix, this.labelColor, - this.onSubmitted - // this.countryList = const [], - // this.onCountryChange, - }); + this.onSubmitted, + // multiline defaults + this.isMultiline = false, + this.minLines = 3, + this.maxLines = 6, + // this.countryList = const [], + // this.onCountryChange, + }); final FocusNode _focusNode = FocusNode(); @@ -113,7 +122,7 @@ class TextInputWidget extends StatelessWidget { children: [ Container( padding: padding, - height: 64.h, + height: isMultiline ? null : 64.h, alignment: Alignment.center, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: Colors.white, @@ -205,9 +214,7 @@ class TextInputWidget extends StatelessWidget { initialDate: DateTime.now(), fontFamily: appState.getLanguageCode() == "ar" ? "GESSTwo" : "Poppins", okWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)), - cancelWidget: Padding( - padding: EdgeInsets.only(right: 8.h), - child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)), + cancelWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)), onCalendarTypeChanged: (bool value) { isGregorian = value; }); @@ -240,7 +247,7 @@ class TextInputWidget extends StatelessWidget { return TextField( enabled: isEnable, scrollPadding: EdgeInsets.zero, - keyboardType: keyboardType, + keyboardType: isMultiline ? TextInputType.multiline : keyboardType, controller: controller, readOnly: isReadOnly, textAlignVertical: TextAlignVertical.top, @@ -255,7 +262,15 @@ class TextInputWidget extends StatelessWidget { FocusManager.instance.primaryFocus?.unfocus(); }, onSubmitted: onSubmitted, - style: TextStyle(fontSize: fontS, height: isWalletAmountInput! ? 1 / 4 : 0, fontWeight: FontWeight.w500, color: AppColors.textColor, letterSpacing: -1), + minLines: isMultiline ? minLines : 1, + maxLines: isMultiline ? maxLines : 1, + style: TextStyle( + fontSize: fontS, + height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0), + fontWeight: FontWeight.w500, + color: AppColors.textColor, + letterSpacing: -1, + ), decoration: InputDecoration( isDense: true, hintText: hintText, diff --git a/lib/widgets/map/HMSMap.dart b/lib/widgets/map/HMSMap.dart index f655479c..7b9c5533 100644 --- a/lib/widgets/map/HMSMap.dart +++ b/lib/widgets/map/HMSMap.dart @@ -4,6 +4,8 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:huawei_map/huawei_map.dart' ; class HMSMap extends StatefulWidget{ @@ -55,7 +57,7 @@ class _HMSMapState extends State { visible: widget.showCenterMarker, child: Align( alignment: Alignment.center, - child: Utils.buildSvgWithAssets(icon: AppAssets.pin_location, width: 24.w, height: 36.h), + child: Icon(Icons.location_pin, size: 36.h, color: AppColors.primaryRedColor).paddingOnly(bottom: 24.h), ), ) ], diff --git a/lib/widgets/map/map.dart b/lib/widgets/map/gms_map.dart similarity index 74% rename from lib/widgets/map/map.dart rename to lib/widgets/map/gms_map.dart index 0c67f1b6..6d046921 100644 --- a/lib/widgets/map/map.dart +++ b/lib/widgets/map/gms_map.dart @@ -5,6 +5,8 @@ import 'package:google_maps_flutter/google_maps_flutter.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/widget_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; class GMSMap extends StatelessWidget{ Completer? controller; @@ -29,14 +31,15 @@ class GMSMap extends StatelessWidget{ children: [ GoogleMap( mapType: mapType, - zoomControlsEnabled: false, - myLocationEnabled: myLocationEnabled, - myLocationButtonEnabled: false, + zoomControlsEnabled: true, + myLocationEnabled: myLocationEnabled, + myLocationButtonEnabled: false, compassEnabled: compassEnabled, initialCameraPosition: currentLocation, onCameraMove: (value) => onCameraMoved(value), onCameraIdle: ()=>onCameraIdle(), - onMapCreated: (GoogleMapController controller) { + // padding: EdgeInsets.only(bottom: 300.h), + onMapCreated: (GoogleMapController controller) { this.controller?.complete(controller); }, ), @@ -44,10 +47,10 @@ class GMSMap extends StatelessWidget{ visible: showCenterMarker, child: Align( alignment: Alignment.center, - child: Utils.buildSvgWithAssets(icon: AppAssets.pin_location, width: 24.w, height: 36.h), - ), - ) - ], - ); + child: Icon(Icons.location_pin, size: 36.h, color: AppColors.primaryRedColor).paddingOnly(bottom: 24.h), + ), + ) + ], + ); } } \ No newline at end of file diff --git a/lib/widgets/map/map_utility_screen.dart b/lib/widgets/map/map_utility_screen.dart new file mode 100644 index 00000000..39821801 --- /dev/null +++ b/lib/widgets/map/map_utility_screen.dart @@ -0,0 +1,246 @@ +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_export.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/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/AmbulanceCallingPlace.dart'; +import 'package:hmg_patient_app_new/features/location/GeocodeResponse.dart'; +import 'package:hmg_patient_app_new/features/location/PlaceDetails.dart'; +import 'package:hmg_patient_app_new/features/location/PlacePrediction.dart'; +import 'package:hmg_patient_app_new/features/location/location_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_doctor_card.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/AddressItem.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/appointment_bottom_sheet.dart' show AppointmentBottomSheet; +import 'package:hmg_patient_app_new/presentation/emergency_services/widgets/location_input_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/CustomSwitch.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/expandable_bottom_sheet/ExpandableBottomSheet.dart'; +import 'package:hmg_patient_app_new/widgets/expandable_bottom_sheet/model/BottomSheetType.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:hmg_patient_app_new/widgets/map/HMSMap.dart'; +import 'package:hmg_patient_app_new/widgets/map/gms_map.dart'; +import 'package:provider/provider.dart'; + +import '../../../widgets/common_bottom_sheet.dart'; + +/// screen to be used to get the location desired by the user +/// to place the values in the request. +/// [confirmButtonString] button text that will be displayed on the button +/// [titleString] bottom sheet title +/// [subTitleString] bottom sheet subtitle for details +/// [onCrossClicked] if something has to be done if the user close the screen +/// [isGmsAvailable] shows if the device that is running the application is GMS or HMS +/// +/// it results [true] if the user clicks on the submit button +/// and [false] if the user closes the screen without giving the consent to proceed for the request +class MapUtilityScreen extends StatelessWidget { + + final String confirmButtonString; + final String titleString; + final String subTitleString; + final bool isGmsAvailable; + final VoidCallback? onCrossClicked; + + const MapUtilityScreen({super.key, required this.confirmButtonString, required this.titleString, required this.subTitleString, required this.isGmsAvailable, this.onCrossClicked}); + + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButton: Padding( + padding: EdgeInsetsDirectional.only(end: 8.h, bottom: 68.h), + child: DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, borderRadius: 12.h), + child: Utils.buildSvgWithAssets( + icon: AppAssets.locate_me, width: 24.h, height: 24.h) + .paddingAll(12.h) + .onPress(() { + context + .read() + .moveToCurrentLocation(); + }), + ), + ), + bottomSheet: FixedBottomSheet(context), + body: Stack( + children: [ + if (isGmsAvailable) + GMSMap( + currentLocation: + context.read().getGMSLocation(), + onCameraMoved: (value) => context + .read() + .handleGMSMapCameraMoved(value), + onCameraIdle: + context.read().handleOnCameraIdle, + myLocationEnabled: true, + inputController: + context.read().gmsController, + showCenterMarker: true, + ) + else + HMSMap( + currentLocation: + context.read().getHMSLocation(), + onCameraMoved: (value) => context + .read() + .handleHMSMapCameraMoved(value), + onCameraIdle: + context.read().handleOnCameraIdle, + myLocationEnabled: false, + inputController: + context.read().hmsController, + showCenterMarker: true, + ), + Align( + alignment: AlignmentDirectional.topStart, + child: Utils.buildSvgWithAssets( + icon: AppAssets.closeBottomNav, width: 32.h, height: 32.h) + .onPress(() { + onCrossClicked?.call(); + // context + // .read() + // .flushPickupInformation(); + + Navigator.pop(context, false); + }), + ).paddingOnly(top: 51.h, left: 24.h), + ], + ), + ); + } + + Widget FixedBottomSheet(BuildContext context) { + return GestureDetector( + onVerticalDragUpdate: (details){ + }, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + spacing: 24.h, + children: [ + inputFields(context).paddingSymmetrical(16.h, 0.h), + SizedBox( + child: DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.scaffoldBgColor, + customBorder: BorderRadius.only( + topLeft: Radius.circular(24.h), + topRight: Radius.circular(24.h), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 24.h, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4.h, + children: [ + titleString.toText21( + weight: FontWeight.w600, + color: AppColors.textColor, + ), + subTitleString.needTranslation.toText12( + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ) + ], + ), + CustomButton( + text: confirmButtonString.needTranslation, + onPressed: () { + ///indicates that the screen has resulted success and should be closed + Navigator.pop(context,true); + }, + ) + ], + ).paddingOnly(top: 24.h, bottom: 32.h, left: 24.h, right: 24.h), + ), + ), + ], + ), + ], + ), + ); + } + + leadingIcon(String leadingIcon) { + return Container( + height: 40.h, + width: 40.h, + margin: EdgeInsets.only(right: 10.h), + padding: EdgeInsets.all(8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + borderRadius: 12.h, + color: AppColors.greyColor, + ), + child: Utils.buildSvgWithAssets(icon: leadingIcon), + ); + } + + + + textPlaceInput(context) { + return Consumer(builder: (_, vm, __) { + return SizedBox( + width: MediaQuery.sizeOf(context).width, + child: TextInputWidget( + labelText: "Enter Pickup Location Manually".needTranslation, + hintText: "Enter Pickup Location".needTranslation, + controller: TextEditingController( + text: vm.geocodeResponse?.results.first.formattedAddress ?? + vm.selectedPrediction?.description, + ), + leadingIcon: AppAssets.location_pickup, + isAllowLeadingIcon: true, + isEnable: false, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), + ).onPress(() { + openLocationInputBottomSheet(context); + }), + ); + }); + } + + ///decide which field to show first based on the selected calling place + Widget inputFields(BuildContext context) { + return textPlaceInput(context); + } + + openLocationInputBottomSheet(BuildContext context) { + context.read().flushSearchPredictions(); + showCommonBottomSheetWithoutHeight( + title: "".needTranslation, + context, + child: SizedBox( + height: MediaQuery.sizeOf(context).height * .8, + child: LocationInputBottomSheet(), + ), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () {}, + ); + } +} diff --git a/lib/widgets/media_viewer/full_screen_image_viewer.dart b/lib/widgets/media_viewer/full_screen_image_viewer.dart new file mode 100644 index 00000000..0fb92f0e --- /dev/null +++ b/lib/widgets/media_viewer/full_screen_image_viewer.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class FullScreenImageViewer extends StatelessWidget { + final bool isSvg; + final String path; + + const FullScreenImageViewer({super.key, required this.isSvg, required this.path}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.black, + leading: IconButton( + icon: Icon(Icons.close, color: AppColors.whiteColor), + onPressed: () => Navigator.of(context).pop(), + ), + ), + backgroundColor: Colors.black, + body: Center( + child: InteractiveViewer( + child: isSvg + ? SvgPicture.asset(path, width: double.infinity, fit: BoxFit.contain) + : Image.asset(width: double.infinity, path, fit: BoxFit.contain), + ), + ), + ); + } +} diff --git a/lib/widgets/radio_list_tile_widget.dart b/lib/widgets/radio_list_tile_widget.dart new file mode 100644 index 00000000..9025e190 --- /dev/null +++ b/lib/widgets/radio_list_tile_widget.dart @@ -0,0 +1,89 @@ +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'; + +class RadioListTileWidget extends StatelessWidget { + final T value; + final T? groupValue; + final String title; + final Widget? subtitleWidget; + final String? subtitle; + final ValueChanged onChanged; + final bool enabled; + + const RadioListTileWidget({ + super.key, + required this.value, + required this.groupValue, + required this.title, + this.subtitleWidget, + this.subtitle, + required this.onChanged, + this.enabled = true, + }); + + @override + Widget build(BuildContext context) { + final bool isSelected = value == groupValue; + + return InkWell( + onTap: enabled ? () => onChanged(value) : null, + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 6.h), + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Container( + width: 20.h, + height: 20.h, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: isSelected ? AppColors.primaryRedColor : const Color(0xff9E9E9E), + width: 2, + ), + ), + child: isSelected + ? Center( + child: Container( + width: 10.h, + height: 10.h, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColors.primaryRedColor, + ), + ), + ) + : null, + ), + SizedBox(width: 16.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.toText15(weight: FontWeight.w500, letterSpacing: -0.64), + if (subtitleWidget != null) ...[subtitleWidget!], + if (subtitle != null) ...[ + SizedBox(height: 4.h), + title.toText13( + weight: FontWeight.w400, + color: AppColors.greyTextColor, + letterSpacing: -0.56, + ), + ], + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/shimmer/movies_shimmer_widget.dart b/lib/widgets/shimmer/common_shimmer_widget.dart similarity index 93% rename from lib/widgets/shimmer/movies_shimmer_widget.dart rename to lib/widgets/shimmer/common_shimmer_widget.dart index fb1af253..d6a2906d 100644 --- a/lib/widgets/shimmer/movies_shimmer_widget.dart +++ b/lib/widgets/shimmer/common_shimmer_widget.dart @@ -1,10 +1,10 @@ +import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:flutter/material.dart'; -class MoviesShimmerWidget extends StatelessWidget { - const MoviesShimmerWidget({super.key}); +class CommonShimmerWidget extends StatelessWidget { + const CommonShimmerWidget({super.key}); @override Widget build(BuildContext context) {