diff --git a/README.md b/README.md index e50fc23..1a459e3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # HMG Patient App -A comprehensive Flutter-based mobile application for HMG (Hospital Management Group) patients, +A comprehensive Flutter-based mobile application for HMG (Habib Medical Group) patients, providing seamless healthcare services and patient management features. ## 📱 Features @@ -162,5 +162,4 @@ flutter build ios --release This project is proprietary software developed for HMG Healthcare Group. - **Built with ❤️ for better healthcare accessibility** diff --git a/android/agconnect-services.json b/android/agconnect-services.json deleted file mode 100644 index 91aad2c..0000000 --- a/android/agconnect-services.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "agcgw":{ - "backurl":"connect-drcn.hispace.hicloud.com", - "url":"connect-drcn.dbankcloud.cn", - "websocketbackurl":"connect-ws-drcn.hispace.dbankcloud.com", - "websocketurl":"connect-ws-drcn.hispace.dbankcloud.cn" - }, - "agcgw_all":{ - "CN":"connect-drcn.dbankcloud.cn", - "CN_back":"connect-drcn.hispace.hicloud.com", - "DE":"connect-dre.dbankcloud.cn", - "DE_back":"connect-dre.hispace.hicloud.com", - "RU":"connect-drru.dbankcloud.cn", - "RU_back":"connect-drru.hispace.hicloud.com", - "SG":"connect-dra.dbankcloud.cn", - "SG_back":"connect-dra.hispace.hicloud.com" - }, - "client":{ - "cp_id":"2640966000002322881", - "product_id":"736430079244816567", - "client_id":"563735388191982656", - "client_secret":"650C7C799812AFFD53A10C7756CF05FB9F215A7E49032ABA8EBF3E14B77535CF", - "project_id":"736430079244816567", - "app_id":"102857389", - "api_key":"CgB6e3x9DJzMgRCmnT6dyUEkp6UsIfddb6l3w0ZEXzeiRMHEFi3400Z5fJ5qaHneU0OrAI/JRpk+DMGVs3QpUxlI", - "package_name":"com.ejada.hmg" - }, - "oauth_client":{ - "client_id":"102857389", - "client_type":1 - }, - "app_info":{ - "app_id":"102857389", - "package_name":"com.ejada.hmg" - }, - "service":{ - "analytics":{ - "collector_url":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn", - "collector_url_ru":"datacollector-drru.dt.hicloud.com,datacollector-drru.dt.dbankcloud.cn", - "collector_url_sg":"datacollector-dra.dt.hicloud.com,datacollector-dra.dt.dbankcloud.cn", - "collector_url_de":"datacollector-dre.dt.hicloud.com,datacollector-dre.dt.dbankcloud.cn", - "collector_url_cn":"datacollector-drcn.dt.hicloud.com,datacollector-drcn.dt.dbankcloud.cn", - "resource_id":"p1", - "channel_id":"" - }, - "search":{ - "url":"https://search-drcn.cloud.huawei.com" - }, - "cloudstorage":{ - "storage_url":"https://agc-storage-drcn.platform.dbankcloud.cn" - }, - "ml":{ - "mlservice_url":"ml-api-drcn.ai.dbankcloud.com,ml-api-drcn.ai.dbankcloud.cn" - } - }, - "region":"CN", - "configuration_version":"3.0", - "appInfos":[ - { - "package_name":"com.ejada.hmg", - "client":{ - "app_id":"102857389" - }, - "app_info":{ - "package_name":"com.ejada.hmg", - "app_id":"102857389" - }, - "oauth_client":{ - "client_type":1, - "client_id":"102857389" - } - } - ] -} diff --git a/android/app/key b/android/app/key new file mode 100644 index 0000000..15b9f5d Binary files /dev/null and b/android/app/key differ diff --git a/android/app/keystores/keystore b/android/app/keystores/keystore new file mode 100644 index 0000000..e69de29 diff --git a/android/app/libs/PenNavUI.aar b/android/app/libs/PenNavUI.aar new file mode 100644 index 0000000..d423bc1 Binary files /dev/null and b/android/app/libs/PenNavUI.aar differ diff --git a/android/app/libs/Penguin.aar b/android/app/libs/Penguin.aar new file mode 100644 index 0000000..5c789c6 Binary files /dev/null and b/android/app/libs/Penguin.aar differ diff --git a/android/app/libs/PenguinRenderer.aar b/android/app/libs/PenguinRenderer.aar new file mode 100644 index 0000000..b657ac6 Binary files /dev/null and b/android/app/libs/PenguinRenderer.aar differ diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b8bd69e..6a1c47e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -248,9 +248,6 @@ - + + + + + + + + Gradle Configuration Cache + + + +
+ +
+ Loading... +
+ + + + + + diff --git a/android/key b/android/key new file mode 100644 index 0000000..15b9f5d Binary files /dev/null and b/android/key differ diff --git a/assets/images/svg/ambulance.svg b/assets/images/svg/ambulance.svg new file mode 100644 index 0000000..a73cfdb --- /dev/null +++ b/assets/images/svg/ambulance.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/pin_location.svg b/assets/images/svg/pin_location.svg new file mode 100644 index 0000000..1a8012d --- /dev/null +++ b/assets/images/svg/pin_location.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index be64c53..2eab03a 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -455,6 +455,8 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = 3A359E86ZF; ENABLE_BITCODE = NO; @@ -467,6 +469,7 @@ MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -637,7 +640,9 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = 3A359E86ZF; ENABLE_BITCODE = NO; @@ -650,6 +655,7 @@ MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -664,6 +670,8 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = 3A359E86ZF; ENABLE_BITCODE = NO; @@ -676,6 +684,7 @@ MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = "com.HMG.HMG-Smartphone"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -693,7 +702,7 @@ 331C808A294A63A400263BE5 /* Profile */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = Debug; }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; @@ -703,7 +712,7 @@ 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = Debug; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; @@ -713,7 +722,7 @@ 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = Debug; }; /* End XCConfigurationList section */ }; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 69204f3..b276dd7 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -2,13 +2,14 @@ import Flutter import UIKit import FirebaseCore import FirebaseMessaging - +import GoogleMaps @main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { + GMSServices.provideAPIKey("AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng") FirebaseApp.configure() GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 73c3fe6..162f0fd 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -88,16 +88,16 @@ class ApiClientImp implements ApiClient { @override post( - String endPoint, { - required Map body, - required Function(dynamic response, int statusCode, {int? messageStatus, String? errorMessage}) onSuccess, - required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure, - bool isAllowAny = false, - bool isExternal = false, - bool isRCService = false, - bool isPaymentServices = false, - bool bypassConnectionCheck = true, - }) async { + String endPoint, { + required Map body, + required Function(dynamic response, int statusCode, {int? messageStatus, String? errorMessage}) onSuccess, + required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure, + bool isAllowAny = false, + bool isExternal = false, + bool isRCService = false, + bool isPaymentServices = false, + bool bypassConnectionCheck = true, + }) async { String url; if (isExternal) { url = endPoint; @@ -119,7 +119,8 @@ class ApiClientImp implements ApiClient { } else {} if (body.containsKey('isDentalAllowedBackend')) { - body['isDentalAllowedBackend'] = body.containsKey('isDentalAllowedBackend') ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND : IS_DENTAL_ALLOWED_BACKEND; + body['isDentalAllowedBackend'] = + body.containsKey('isDentalAllowedBackend') ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND : IS_DENTAL_ALLOWED_BACKEND; } if (!body.containsKey('IsPublicRequest')) { @@ -136,9 +137,9 @@ class ApiClientImp implements ApiClient { body['PatientType'] = PATIENT_TYPE_ID.toString(); } - // TODO : These should be from the appState if (user != null) { body['TokenID'] = body['TokenID'] ?? token; + body['PatientID'] = body['PatientID'] ?? user.patientId; body['PatientOutSA'] = body.containsKey('PatientOutSA') ? body['PatientOutSA'] ?? user.outSa : user.outSa; @@ -174,7 +175,8 @@ class ApiClientImp implements ApiClient { } // body['TokenID'] = "@dm!n"; - // body['PatientID'] = 3966014; + // body['PatientID'] = 4772429; + // body['PatientID'] = 1231755; // body['PatientTypeID'] = 1; // // body['PatientOutSA'] = 0; @@ -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) { @@ -210,35 +213,43 @@ class ApiClientImp implements ApiClient { onSuccess(parsed, statusCode, messageStatus: 1, errorMessage: ""); } else { onSuccess(parsed, statusCode, - messageStatus: parsed.contains('MessageStatus') ? parsed['MessageStatus'] : 1, errorMessage: parsed.contains('ErrorEndUserMessage') ? parsed['ErrorEndUserMessage'] : ""); + messageStatus: parsed.contains('MessageStatus') ? parsed['MessageStatus'] : 1, + errorMessage: parsed.contains('ErrorEndUserMessage') ? parsed['ErrorEndUserMessage'] : ""); } } else { if (parsed['Response_Message'] != null) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, + messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else { if (parsed['ErrorType'] == 4) { //TODO : handle app update - onFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, failureType: AppUpdateFailure("parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']")); + onFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, + failureType: AppUpdateFailure("parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']")); logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); } if (parsed['ErrorType'] == 2) { - // todo: handle Logout + // todo_section: handle Logout onFailure( parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, - failureType: UnAuthenticatedUserFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'] ?? "User is not Authenticated", url: url), + failureType: + UnAuthenticatedUserFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'] ?? "User is not Authenticated", url: url), ); // logApiEndpointError(endPoint, "session logged out", statusCode); } if (isAllowAny) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, + messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else if (parsed['IsAuthenticated'] == null) { if (parsed['isSMSSent'] == true) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, + messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else if (parsed['MessageStatus'] == 1) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, + messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else if (parsed['Result'] == 'OK') { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, + messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else { onFailure( parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], @@ -248,16 +259,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 +290,6 @@ class ApiClientImp implements ApiClient { logApiEndpointError(endPoint, parsed['ErrorSearchMsg'], statusCode); } } else { - onFailure( parsed['message'] ?? parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode, @@ -287,7 +300,8 @@ class ApiClientImp implements ApiClient { } } else { if (parsed['SameClinicApptList'] != null) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + onSuccess(parsed, statusCode, + messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else { if (parsed['message'] != null) { onFailure( diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index d7da819..b21d329 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -1,9 +1,5 @@ -import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:amazon_payfort/amazon_payfort.dart'; - -var MAX_SMALL_SCREEN = 660; -final OPENTOK_API_KEY = '46209962'; -// final OPENTOK_API_KEY = '47464241'; +import 'package:hmg_patient_app_new/core/enums.dart'; // PACKAGES and OFFERS var EXA_CART_API_BASE_URL = 'https://mdlaboratories.com/offersdiscounts'; @@ -48,9 +44,9 @@ var PHARMACY_REDIRECT_URL = 'https://bit.ly/AlhabibPharmacy'; // var PHARMACY_BASE_URL = 'https://mdlaboratories.com/exacartapitest/api/'; // RC API URL -var RC_BASE_URL = 'https://rc.hmg.com/'; +// var RC_BASE_URL = 'https://rc.hmg.com/'; -// var RC_BASE_URL = 'https://rc.hmg.com/test/'; +var RC_BASE_URL = 'https://rc.hmg.com/uat/'; // var RC_BASE_URL = 'https://ms.hmg.com/rc/'; @@ -265,7 +261,6 @@ var CANCEL_APPOINTMENT = "Services/Doctors.svc/REST/CancelAppointment"; var GENERATE_QR_APPOINTMENT = "Services/Doctors.svc/REST/GenerateQRAppointmentNo"; //URL send email appointment QR -var EMAIL_QR_APPOINTMENT = "Services/Notifications.svc/REST/sendEmailForOnLineCheckin"; //URL check payment status var CHECK_PAYMENT_STATUS = "Services/PayFort_Serv.svc/REST/GetRequestStatusByRequestID"; @@ -275,14 +270,8 @@ var CREATE_ADVANCE_PAYMENT = "Services/Doctors.svc/REST/CreateAdvancePayment"; var HIS_CREATE_ADVANCE_PAYMENT = "Services/Patients.svc/REST/HIS_CreateAdvancePayment"; -var ER_CREATE_ADVANCE_PAYMENT = "services/Doctors.svc/REST/ER_CreateAdvancePaymentForClinic"; - -var ER_INSERT_ADVANCE_PAYMENT = "services/Doctors.svc/REST/ER_InsertEROnlinePaymentDetails"; - var ADD_ADVANCE_NUMBER_REQUEST = 'Services/PayFort_Serv.svc/REST/AddAdvancedNumberRequest'; -var GENERATE_ANCILLARY_ORDERS_INVOICE = 'Services/Doctors.svc/REST/AutoGenerateAncillaryOrderInvoice'; - var IS_ALLOW_ASK_DOCTOR = 'Services/Doctors.svc/REST/GetPatientDoctorAppointmentResult'; var GET_CALL_REQUEST_TYPE = 'Services/Doctors.svc/REST/GetCallRequestType_LOV'; var ADD_VIDA_REQUEST = 'Services/ER_VirtualCall.svc/REST/PatientER_VidaRequestInseart'; @@ -308,8 +297,6 @@ var GET_LIVECARE_CLINIC_TIMING = 'Services/ER_VirtualCall.svc/REST/PatientER_Get var GET_ER_APPOINTMENT_FEES = 'Services/DoctorApplication.svc/REST/GetERAppointmentFees'; var GET_ER_APPOINTMENT_TIME = 'Services/ER_VirtualCall.svc/REST/GetRestTime'; -var CHECK_PATIENT_DERMA_PACKAGE = 'Services/OUTPs.svc/REST/getPatientPackageComponentsForOnlineCheckIn'; - var ADD_NEW_CALL_FOR_PATIENT_ER = 'Services/DoctorApplication.svc/REST/NewCallForPatientER'; var GET_LIVECARE_HISTORY = 'Services/ER_VirtualCall.svc/REST/GetPatientErVirtualHistory'; @@ -570,7 +557,8 @@ var GET_CUSTOMER_INFO = "VerifyCustomer"; //Pharmacy -var GET_PHARMACY_CATEGORISE = 'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id=0'; +var GET_PHARMACY_CATEGORISE = + 'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id=0'; var GET_OFFERS_CATEGORISE = 'discountcategories'; var GET_OFFERS_PRODUCTS = 'offerproducts/'; var GET_CATEGORISE_PARENT = 'categories?fields=id,name,namen,description,image,localized_names,display_order,parent_category_id,is_leaf&parent_id='; @@ -720,16 +708,18 @@ 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'; +const FAMILY_FILES = 'Services/Authentication.svc/REST/GetAllSharedRecordsByStatus'; var GET_PRESCRIPTION_INSTRUCTIONS_PDF = 'Services/ChatBot_Service.svc/REST/Chatbot_SendMedicationInstructionByWhatsApp'; class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT @@ -737,10 +727,6 @@ class ApiConsts { static String RCBaseUrl = 'https://rc.hmg.com/'; // RC API URL PROD - static String SELECT_DEVICE_IMEI = 'Services/Patients.svc/REST/Patient_SELECTDeviceIMEIbyIMEI'; - - static num VERSION_ID = 18.9; - static var payFortEnvironment = FortEnvironment.production; static var applePayMerchantId = "merchant.com.hmgwebservices"; @@ -789,7 +775,6 @@ class ApiConsts { RCBaseUrl = 'https://rc.hmg.com/test/'; break; case AppEnvironmentTypeEnum.preProd: - baseUrl = "https://webservices.hmg.com/"; payFortEnvironment = FortEnvironment.production; applePayMerchantId = "merchant.com.hmgwebservices"; @@ -833,7 +818,7 @@ class ApiConsts { static final String getUserAgreementContent = 'Services/Patients.svc/REST/GetUsageAgreementText'; static final String checkPatientForRegistration = 'Services/Authentication.svc/REST/CheckPatientForRegisteration'; - static final String checkUserStatus= 'Services/NHIC.svc/REST/GetPatientInfo'; + static final String checkUserStatus = 'Services/NHIC.svc/REST/GetPatientInfo'; static final String insertPatientDeviceIMEIData = 'Services/Patients.svc/REST/Patient_INSERTDeviceIMEI'; static final String insertPatientMobileData = 'Services/MobileNotifications.svc/REST/Insert_PatientMobileDeviceInfo'; @@ -849,7 +834,19 @@ class ApiConsts { static final String removeFileFromFamilyMembers = 'Services/Authentication.svc/REST/ActiveDeactive_PatientFile'; static final String acceptAndRejectFamilyFile = 'Services/Authentication.svc/REST/Update_FileStatus'; - // static values for Api + // Ancillary Order Apis + static final String getOnlineAncillaryOrderList = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderList'; + static final String getOnlineAncillaryOrderProcList = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderProcList'; + static final String generateAncillaryOrderInvoice = 'Services/Doctors.svc/REST/AutoGenerateAncillaryOrderInvoice'; + static final String autoGenerateAncillaryOrdersInvoice = 'Services/Doctors.svc/REST/AutoGenerateAncillaryOrderInvoice'; + static final String getRequestStatusByRequestID = 'Services/PayFort_Serv.svc/REST/GetRequestStatusByRequestID'; + + //Payment APIs + static final String applePayInsertRequest = "Services/PayFort_Serv.svc/REST/PayFort_ApplePayRequestData_Insert"; + static final String createAdvancePayments = 'Services/Patients.svc/REST/HIS_CreateAdvancePayment'; + static final String addAdvanceNumberRequest = 'Services/PayFort_Serv.svc/REST/AddAdvancedNumberRequest'; + + // ************ static values for Api **************** static final double appVersionID = 18.7; static final int appChannelId = 3; static final String appIpAddress = "10.20.10.20"; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 61cf299..e8215ba 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -25,6 +25,7 @@ class AppAssets { static const String download = '$svgBasePath/download.svg'; static const String language = '$svgBasePath/language.svg'; static const String location = '$svgBasePath/location.svg'; + static const String pin_location = '$svgBasePath/pin_location.svg'; static const String whatsapp = '$svgBasePath/whatsapp.svg'; static const String card_user = '$svgBasePath/card_user.svg'; static const String habiblogo = '$svgBasePath/habiblogo.svg'; @@ -156,6 +157,7 @@ class AppAssets { static const String activeCheck = '$svgBasePath/active-check.svg'; static const String deleteIcon = '$svgBasePath/delete_icon.svg'; static const String call_ambulance_icon = '$svgBasePath/call_ambulance_icon.svg'; + static const String ambulance = '$svgBasePath/ambulance.svg'; static const String nearest_er_icon = '$svgBasePath/nearest_er_icon.svg'; static const String rrt_icon = '$svgBasePath/rrt_icon.svg'; static const String waiting_time_clock = '$svgBasePath/waiting_time_clock.svg'; diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index cbeb4d4..c8659a7 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -56,12 +56,12 @@ class AppState { } FutureOr checkIfGMSAvailableOrNot() async { + isGMSAvailable = (await GmsCheck().checkGmsAvailability()) ?? true; if(Platform.isIOS) { - isGMSAvailable = false; + isGMSAvailable = true; isAndroid = false; return; } - isGMSAvailable = (await GmsCheck().checkGmsAvailability()) ?? true; } AuthenticatedUser? getAuthenticatedUser({bool isFamily = false}) { diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index a82a9ad..37d0cc4 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -8,6 +8,8 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_view_ import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/common/common_repo.dart'; +import 'package:hmg_patient_app_new/features/contact_us/contact_us_repo.dart'; +import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; 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_repo.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; @@ -19,6 +21,8 @@ import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/features/location/location_repo.dart'; +import 'package:hmg_patient_app_new/features/location/location_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; @@ -31,6 +35,8 @@ import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_mo import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; +import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart'; +import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; @@ -40,7 +46,6 @@ import 'package:hmg_patient_app_new/services/localauth_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; -import 'package:http/http.dart'; import 'package:local_auth/local_auth.dart'; import 'package:logger/web.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -98,11 +103,16 @@ class AppDependencies { getIt.registerLazySingleton(() => PrescriptionsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => InsuranceRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => PayfortRepoImp(loggerService: getIt(), apiClient: getIt())); - getIt.registerLazySingleton(() => LocalAuthService(loggerService: getIt(), localAuth: getIt())); + getIt.registerLazySingleton( + () => LocalAuthService(loggerService: getIt(), localAuth: getIt())); getIt.registerLazySingleton(() => HabibWalletRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => MedicalFileRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => ImmediateLiveCareRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => EmergencyServicesRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => TodoSectionRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton( + () => LocationRepoImpl(apiClient: getIt())); + getIt.registerLazySingleton(() => ContactUsRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -158,7 +168,13 @@ class AppDependencies { ); getIt.registerLazySingleton( - () => BookAppointmentsViewModel(bookAppointmentsRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt(), myAppointmentsViewModel: getIt(), locationUtils: getIt(), dialogService: getIt()), + () => BookAppointmentsViewModel( + bookAppointmentsRepo: getIt(), + errorHandlerService: getIt(), + navigationService: getIt(), + myAppointmentsViewModel: getIt(), + locationUtils: getIt(), + dialogService: getIt()), ); getIt.registerLazySingleton( @@ -172,7 +188,13 @@ class AppDependencies { getIt.registerLazySingleton( () => AuthenticationViewModel( - authenticationRepo: getIt(), cacheService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt(), errorHandlerService: getIt(), localAuthService: getIt()), + authenticationRepo: getIt(), + cacheService: getIt(), + navigationService: getIt(), + dialogService: getIt(), + appState: getIt(), + errorHandlerService: getIt(), + localAuthService: getIt()), ); getIt.registerLazySingleton(() => ProfileSettingsViewModel()); @@ -185,8 +207,7 @@ class AppDependencies { ); getIt.registerLazySingleton( - () => - AppointmentViaRegionViewmodel( + () => AppointmentViaRegionViewmodel( navigationService: getIt(), appState: getIt(), ), @@ -199,6 +220,30 @@ class AppDependencies { emergencyServicesRepo: getIt(), appState: getIt(), errorHandlerService: getIt(), + appointmentRepo: getIt(), + dialogService: getIt() + ), + ); + + getIt.registerLazySingleton( + () => LocationViewModel( + locationRepo: getIt(), + errorHandlerService: getIt(), + ), + ); + + getIt.registerLazySingleton( + () => ContactUsViewModel( + contactUsRepo: getIt(), + appState: getIt(), + errorHandlerService: getIt(), + ), + ); + + getIt.registerLazySingleton( + () => TodoSectionViewModel( + todoSectionRepo: getIt(), + errorHandlerService: getIt(), ), ); diff --git a/lib/core/utils/date_util.dart b/lib/core/utils/date_util.dart index 2fbbd09..d58aef6 100644 --- a/lib/core/utils/date_util.dart +++ b/lib/core/utils/date_util.dart @@ -6,7 +6,11 @@ class DateUtil { /// convert String To Date function /// [date] String we want to convert static DateTime convertStringToDate(String? date) { - if (date != null) { + 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); @@ -14,9 +18,7 @@ class DateUtil { return DateTime.fromMillisecondsSinceEpoch(int.parse( date.substring(startIndex + start.length, endIndex), )); - } else { - return DateTime.now(); - } + } static DateTime convertStringToDateSaudiTimezone(String date, int projectId) { diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index a4ea936..e57039c 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:easy_localization/easy_localization.dart'; import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; @@ -96,6 +98,7 @@ class RequestUtils { request.patientIdentificationID = request.nationalID = (registeredData.patientIdentificationId ?? 0); request.dob = registeredData.dob; request.isRegister = registeredData.isRegister; + log("nationIdText: ${nationIdText}"); } else { if (fileNo) { request.patientID = patientId ?? int.parse(nationIdText); @@ -199,7 +202,8 @@ class RequestUtils { return request; } - static dynamic getUserSignupCompletionRequest({String? fullName, String? emailAddress, GenderTypeEnum? gender, MaritalStatusTypeEnum? maritalStatus}) { + static dynamic getUserSignupCompletionRequest( + {String? fullName, String? emailAddress, GenderTypeEnum? gender, MaritalStatusTypeEnum? maritalStatus}) { AppState appState = getIt.get(); bool isDubai = appState.getUserRegistrationPayload.patientOutSa == 1 ? true : false; @@ -215,11 +219,19 @@ class RequestUtils { return { "Patientobject": { "TempValue": true, - "PatientIdentificationType": - (isDubai ? appState.getUserRegistrationPayload.patientIdentificationId?.toString().substring(0, 1) : appState.getNHICUserData.idNumber!.substring(0, 1)) == "1" ? 1 : 2, - "PatientIdentificationNo": isDubai ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : appState.getNHICUserData.idNumber.toString(), + "PatientIdentificationType": (isDubai + ? appState.getUserRegistrationPayload.patientIdentificationId?.toString().substring(0, 1) + : appState.getNHICUserData.idNumber!.substring(0, 1)) == + "1" + ? 1 + : 2, + "PatientIdentificationNo": + isDubai ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : appState.getNHICUserData.idNumber.toString(), "MobileNumber": appState.getUserRegistrationPayload.patientMobileNumber ?? 0, - "PatientOutSA": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || appState.getUserRegistrationPayload.zipCode == '+966') ? 0 : 1, + "PatientOutSA": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || + appState.getUserRegistrationPayload.zipCode == '+966') + ? 0 + : 1, "FirstNameN": isDubai ? "..." : appState.getNHICUserData.firstNameAr, "FirstName": isDubai ? (names.isNotEmpty ? names[0] : "...") : appState.getNHICUserData.firstNameEn, "MiddleNameN": isDubai ? "..." : appState.getNHICUserData.secondNameAr, @@ -233,7 +245,10 @@ class RequestUtils { "eHealthIDField": isDubai ? null : appState.getNHICUserData.healthId, "DateofBirthN": date, "EmailAddress": emailAddress, - "SourceType": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || appState.getUserRegistrationPayload.zipCode == '+966') ? "1" : "2", + "SourceType": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || + appState.getUserRegistrationPayload.zipCode == '+966') + ? "1" + : "2", "PreferredLanguage": appState.getLanguageCode() == "ar" ? (isDubai ? "1" : 1) : (isDubai ? "2" : 2), "Marital": isDubai ? (maritalStatus == MaritalStatusTypeEnum.single @@ -247,20 +262,25 @@ class RequestUtils { ? '1' : '2'), }, - "PatientIdentificationID": isDubai ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : appState.getNHICUserData.idNumber.toString(), + "PatientIdentificationID": + isDubai ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : appState.getNHICUserData.idNumber.toString(), "PatientMobileNumber": appState.getUserRegistrationPayload.patientMobileNumber.toString()[0] == '0' ? appState.getUserRegistrationPayload.patientMobileNumber : '0${appState.getUserRegistrationPayload.patientMobileNumber}', "DOB": dob, "IsHijri": appState.getUserRegistrationPayload.isHijri, - "PatientOutSA": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || appState.getUserRegistrationPayload.zipCode == '+966') ? 0 : 1, + "PatientOutSA": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || + appState.getUserRegistrationPayload.zipCode == '+966') + ? 0 + : 1, "isDentalAllowedBackend": appState.getUserRegistrationPayload.isDentalAllowedBackend, "ZipCode": appState.getUserRegistrationPayload.zipCode, if (!isDubai) "HealthId": appState.getNHICUserData.healthId, }; } - static Future getAddFamilyRequest({required String nationalIDorFile, required String mobileNo, required String countryCode}) async { + static Future getAddFamilyRequest( + {required String nationalIDorFile, required String mobileNo, required String countryCode}) async { FamilyFileRequest request = FamilyFileRequest(); int? loginType = 0; diff --git a/lib/core/utils/size_utils.dart b/lib/core/utils/size_utils.dart index 6566641..fdd0d30 100644 --- a/lib/core/utils/size_utils.dart +++ b/lib/core/utils/size_utils.dart @@ -216,6 +216,7 @@ class SizeUtils { log("longerSide: $longerSide"); log("shorterSide: $shorterSide"); log("isTablet: $isTablet"); + log("isFoldable: $isFoldable"); } } diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 491aa49..6f1dcc6 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -18,6 +18,8 @@ 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/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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -100,6 +102,12 @@ 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 ""; + return appState.isArabic() + ? "${dateTime.day.toString()} ${getMonthArabic(dateTime.month)}, ${dateTime.year.toString()}" + : "${dateTime.day.toString()} ${getMonth(dateTime.month)}, ${dateTime.year.toString()}"; + } /// get month by /// [month] convert month number in to month name @@ -315,7 +323,8 @@ class Utils { crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox(height: isSmallWidget ? 0.h : 48.h), - Lottie.asset(AppAnimations.noData, repeat: false, reverse: false, frameRate: FrameRate(60), width: width.h, height: height.h, fit: BoxFit.fill), + Lottie.asset(AppAnimations.noData, + repeat: false, reverse: false, frameRate: FrameRate(60), width: width.h, height: height.h, fit: BoxFit.fill), SizedBox(height: 16.h), (noDataText ?? LocaleKeys.noDataAvailable.tr()) .toText16(weight: FontWeight.w500, color: AppColors.greyTextColor, isCenter: true) @@ -331,7 +340,8 @@ class Utils { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Lottie.asset(AppAnimations.loadingAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), + Lottie.asset(AppAnimations.loadingAnimation, + repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), SizedBox(height: 8.h), (loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor, isCenter: true), SizedBox(height: 8.h), @@ -357,7 +367,8 @@ class Utils { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Lottie.asset(AppAnimations.errorAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), + Lottie.asset(AppAnimations.errorAnimation, + repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), SizedBox(height: 8.h), (loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor), SizedBox(height: 8.h), @@ -365,12 +376,14 @@ class Utils { ).center; } - static Widget getWarningWidget({String? loadingText, bool isShowActionButtons = false, Widget? bodyWidget, Function? onConfirmTap, Function? onCancelTap}) { + static Widget getWarningWidget( + {String? loadingText, bool isShowActionButtons = false, Widget? bodyWidget, Function? onConfirmTap, Function? onCancelTap}) { return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Lottie.asset(AppAnimations.warningAnimation, repeat: false, reverse: false, frameRate: FrameRate(60), width: 128.h, height: 128.h, fit: BoxFit.fill), + Lottie.asset(AppAnimations.warningAnimation, + repeat: false, reverse: false, frameRate: FrameRate(60), width: 128.h, height: 128.h, fit: BoxFit.fill), SizedBox(height: 8.h), (loadingText ?? LocaleKeys.loadingText.tr()).toText14(color: AppColors.blackColor, letterSpacing: 0), SizedBox(height: 16.h), @@ -690,7 +703,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); }, ); @@ -742,10 +755,14 @@ class Utils { static Widget getPaymentAmountWithSymbol2(num habibWalletAmount, {double iconSize = 14, - Color iconColor = AppColors.textColor, + double? fontSize, + double? letterSpacing, + FontWeight? fontWeight, + Color iconColor = AppColors.textColor, Color textColor = AppColors.blackColor, bool isSaudiCurrency = true, - bool isExpanded = true}) { + bool isExpanded = true, + }) { return RichText( maxLines: 1, text: TextSpan( @@ -756,8 +773,13 @@ class Utils { child: Utils.buildSvgWithAssets(icon: AppAssets.saudi_riyal_icon, width: iconSize.h, height: iconSize.h, iconColor: iconColor), ), TextSpan( - text: " $habibWalletAmount", - style: TextStyle(color: textColor, fontSize: 32.f, letterSpacing: -4, fontWeight: FontWeight.w600, height: 1), + text: NumberFormat.currency(locale: 'en_US', symbol: " ", decimalDigits: 0).format(habibWalletAmount), + style: TextStyle( + color: textColor, + fontSize: fontSize ?? 32.f, + letterSpacing: letterSpacing??-4, + fontWeight: fontWeight ?? FontWeight.w600, + height: 1), ), ], ), @@ -799,11 +821,27 @@ 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; } + ///method to be used to get the text as per the langauge of the application + static String getTextWRTCurrentLanguage(String? englishText, String? arabicText) { + String? text = appState.isArabic() ? arabicText : englishText; + return text ?? ''; + } + + static String formatNumberToInternationalFormat(num number, {String? currencySymbol, int decimalDigit = 0}) { + return NumberFormat.currency(locale: 'en_US', symbol: currencySymbol ?? "", decimalDigits: decimalDigit).format(number); + } + + 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); + } + static bool havePrivilege(int id) { bool isHavePrivilege = false; try { @@ -815,4 +853,7 @@ class Utils { } return isHavePrivilege; } + + + } diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 041e8b7..1a6d1cc 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -279,11 +279,11 @@ extension EmailValidator on String { height: 1, color: color ?? AppColors.blackColor, fontSize: 22.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); - Widget toText24({Color? color, bool isBold = false, bool isCenter = false, FontWeight? fontWeight}) => Text( + Widget toText24({Color? color, bool isBold = false, bool isCenter = false, FontWeight? fontWeight, double? letterSpacing}) => Text( this, textAlign: isCenter ? TextAlign.center : null, style: TextStyle( - height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.f, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.f, letterSpacing: letterSpacing??-1, fontWeight: isBold ? FontWeight.bold : fontWeight??FontWeight.normal), ); Widget toText26({Color? color, bool isBold = false, double? height, bool isCenter = false, FontWeight? weight, double? letterSpacing}) => Text( diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 8be2b8e..3260ea5 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -245,6 +245,7 @@ class AuthenticationViewModel extends ChangeNotifier { } Future _handleNewImeiRegistration() async { + await selectDeviceImei(onSuccess: (dynamic respData) async { try { if (respData != null) { @@ -338,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/contact_us/contact_us_repo.dart b/lib/features/contact_us/contact_us_repo.dart new file mode 100644 index 0000000..f2b1169 --- /dev/null +++ b/lib/features/contact_us/contact_us_repo.dart @@ -0,0 +1,95 @@ +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/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/services/logger_service.dart'; + +abstract class ContactUsRepo { + Future>>> getHMGLocations(); + + Future>>> getLiveChatProjectsList(); +} + +class ContactUsRepoImp implements ContactUsRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + ContactUsRepoImp({required this.apiClient, required this.loggerService}); + + @override + Future>>> getHMGLocations() async { + Map mapDevice = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_FINDUS_REQUEST, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['ListHMGLocation']; + final hmgLocations = list.map((item) => GetHMGLocationsModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: hmgLocations, + ); + } 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>>> getLiveChatProjectsList() async { + Map mapDevice = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_LIVECHAT_REQUEST, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['List_PatientICProjects']; + final hmgLocations = list.map((item) => GetPatientICProjectsModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: hmgLocations, + ); + } 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/contact_us/contact_us_view_model.dart b/lib/features/contact_us/contact_us_view_model.dart new file mode 100644 index 0000000..7826bd1 --- /dev/null +++ b/lib/features/contact_us/contact_us_view_model.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/features/contact_us/contact_us_repo.dart'; +import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_hmg_locations.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class ContactUsViewModel extends ChangeNotifier { + ContactUsRepo contactUsRepo; + ErrorHandlerService errorHandlerService; + AppState appState; + + bool isHMGLocationsListLoading = false; + bool isHMGHospitalsListSelected = true; + + List hmgHospitalsLocationsList = []; + List hmgPharmacyLocationsList = []; + + ContactUsViewModel({required this.contactUsRepo, required this.errorHandlerService, required this.appState}); + + initContactUsViewModel() { + isHMGLocationsListLoading = true; + isHMGHospitalsListSelected = true; + hmgHospitalsLocationsList.clear(); + hmgPharmacyLocationsList.clear(); + getHMGLocations(); + notifyListeners(); + } + + setHMGHospitalsListSelected(bool isSelected) { + isHMGHospitalsListSelected = isSelected; + notifyListeners(); + } + + Future getHMGLocations({Function(dynamic)? onSuccess, Function(String)? onError}) async { + isHMGLocationsListLoading = true; + hmgHospitalsLocationsList.clear(); + hmgPharmacyLocationsList.clear(); + notifyListeners(); + + final result = await contactUsRepo.getHMGLocations(); + + 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) { + // hmgLocationsList = apiResponse.data!; + for (var location in apiResponse.data!) { + if (location.locationType == 1) { + hmgHospitalsLocationsList.add(location); + } else if (location.locationType == 2) { + hmgPharmacyLocationsList.add(location); + } + } + isHMGLocationsListLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } +} diff --git a/lib/features/contact_us/models/resp_models/get_hmg_locations.dart b/lib/features/contact_us/models/resp_models/get_hmg_locations.dart new file mode 100644 index 0000000..d15b853 --- /dev/null +++ b/lib/features/contact_us/models/resp_models/get_hmg_locations.dart @@ -0,0 +1,80 @@ +class GetHMGLocationsModel { + dynamic cityID; + String? cityName; + dynamic cityNameN; + dynamic distanceInKilometers; + bool? isActive; + String? latitude; + int? locationID; + String? locationName; + dynamic locationNameN; + dynamic locationType; + String? longitude; + int? pharmacyLocationID; + String? phoneNumber; + int? projectID; + String? projectImageURL; + int? setupID; + dynamic sortOrder; + + GetHMGLocationsModel( + {this.cityID, + this.cityName, + this.cityNameN, + this.distanceInKilometers, + this.isActive, + this.latitude, + this.locationID, + this.locationName, + this.locationNameN, + this.locationType, + this.longitude, + this.pharmacyLocationID, + this.phoneNumber, + this.projectID, + this.projectImageURL, + this.setupID, + this.sortOrder}); + + GetHMGLocationsModel.fromJson(Map json) { + cityID = json['CityID']; + cityName = json['CityName']; + cityNameN = json['CityNameN']; + distanceInKilometers = json['DistanceInKilometers']; + isActive = json['IsActive']; + latitude = json['Latitude']; + locationID = json['LocationID']; + locationName = json['LocationName']; + locationNameN = json['LocationNameN']; + locationType = json['LocationType']; + longitude = json['Longitude']; + pharmacyLocationID = json['PharmacyLocationID']; + phoneNumber = json['PhoneNumber']; + projectID = json['ProjectID']; + projectImageURL = json['ProjectImageURL']; + setupID = json['SetupID']; + sortOrder = json['SortOrder']; + } + + Map toJson() { + final Map data = new Map(); + data['CityID'] = this.cityID; + data['CityName'] = this.cityName; + data['CityNameN'] = this.cityNameN; + data['DistanceInKilometers'] = this.distanceInKilometers; + data['IsActive'] = this.isActive; + data['Latitude'] = this.latitude; + data['LocationID'] = this.locationID; + data['LocationName'] = this.locationName; + data['LocationNameN'] = this.locationNameN; + data['LocationType'] = this.locationType; + data['Longitude'] = this.longitude; + data['PharmacyLocationID'] = this.pharmacyLocationID; + data['PhoneNumber'] = this.phoneNumber; + data['ProjectID'] = this.projectID; + data['ProjectImageURL'] = this.projectImageURL; + data['SetupID'] = this.setupID; + data['SortOrder'] = this.sortOrder; + return data; + } +} \ No newline at end of file diff --git a/lib/features/contact_us/models/resp_models/get_patient_ic_projects.dart b/lib/features/contact_us/models/resp_models/get_patient_ic_projects.dart new file mode 100644 index 0000000..ba7463f --- /dev/null +++ b/lib/features/contact_us/models/resp_models/get_patient_ic_projects.dart @@ -0,0 +1,48 @@ +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; + +class GetPatientICProjectsModel { + int? id; + String? projectName; + String? projectNameN; + String? value; + dynamic languageId; + DateTime? createdOn; + String? createdBy; + dynamic editedOn; + dynamic editedBy; + bool? isActive; + dynamic distanceInKilometers; + + GetPatientICProjectsModel( + {this.id, this.projectName, this.projectNameN, this.value, this.languageId, this.createdOn, this.createdBy, this.editedOn, this.editedBy, this.distanceInKilometers, this.isActive}); + + GetPatientICProjectsModel.fromJson(Map json) { + id = json['id']; + projectName = json['ProjectName']; + projectNameN = json['ProjectNameN']; + value = json['Value']; + languageId = json['LanguageId']; + createdOn = DateUtil.convertStringToDate(json['CreatedOn']); + createdBy = json['CreatedBy']; + editedOn = json['EditedOn']; + editedBy = json['EditedBy']; + isActive = json['IsActive']; + distanceInKilometers = json['DistanceInKilometers']; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['ProjectName'] = this.projectName; + data['ProjectNameN'] = this.projectNameN; + data['Value'] = this.value; + data['LanguageId'] = this.languageId; + data['CreatedOn'] = this.createdOn; + data['CreatedBy'] = this.createdBy; + data['EditedOn'] = this.editedOn; + data['EditedBy'] = this.editedBy; + data['IsActive'] = this.isActive; + data['DistanceInKilometers'] = this.distanceInKilometers; + return data; + } +} diff --git a/lib/features/emergency_services/emergency_services_repo.dart b/lib/features/emergency_services/emergency_services_repo.dart index 089e7ad..c54c3d1 100644 --- a/lib/features/emergency_services/emergency_services_repo.dart +++ b/lib/features/emergency_services/emergency_services_repo.dart @@ -1,16 +1,56 @@ +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/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart'; -import 'package:hmg_patient_app_new/features/emergency_services/models/resp_models/rrt_procedures_response_model.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/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/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'; +import 'models/resp_model/PatientERTransportationMethod.dart'; + abstract class EmergencyServicesRepo { Future>>> getRRTProcedures(); Future>>> getNearestEr({int? id, int? projectID}); + + Future>> checkPatientERAdvanceBalance(); + + Future>>> getProjectList(); + + Future>> checkPatientERPaymentInformation({int projectID}); + + 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>> getProjectIDFromNFC({required String nfcCode}); + + Future>> autoGenerateInvoiceERClinic({required int projectID}); + + Future>>> getTransportationMethods({int? id}); + + + Future>> submitAmbulanceRequest(PatientER_RC request); + + Future>>> getTransportationOrders({int? id}); + + Future>> cancelOrder(int? iD, int patientId); + } class EmergencyServicesRepoImp implements EmergencyServicesRepo { @@ -35,7 +75,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, @@ -57,7 +98,7 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { @override Future>>> getRRTProcedures() async { - Map mapDevice = {}; + Map mapDevice = {"ProjectID": 15}; try { GenericApiModel>? apiResponse; @@ -71,7 +112,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, @@ -91,4 +133,430 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getTransportationMethods({int? id}) async { + Map mapDevice = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.get( + "$GET_ALL_TRANSPORTATIONS_RC?patientID=$id", + isRCService: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['response']['transportationservices']; + final proceduresList = list.map((item) => PatientERTransportationMethod.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: proceduresList, + ); + } 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>>> getProjectList() async { + Map request = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_PROJECT_LIST, + body: request, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['ListProject']; + + final appointmentsList = list.map((item) => HospitalsModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: appointmentsList, + ); + } 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>> submitAmbulanceRequest(PatientER_RC request) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + body: request.toJson(), + INSERT_TRANSPORTATION_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>>> getTransportationOrders({int? id}) async { + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + body: {}, + "$GET_ALL_TRANSPORTATIONS_ORDERS?patientID=$id", + isRCService: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['response']; + final proceduresList = list.map((item) => AmbulanceRequestOrdersModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: proceduresList, + ); + } 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>> cancelOrder(int? iD, int patientID) async { + try { + GenericApiModel? apiResponse; + + Map request = {}; + request['Id'] = iD; + request['StatusId'] = 6; + request['ClickButton'] = 14; + request['PatientID'] = patientID; + Failure? failure; + await apiClient.post( + body: request, + CANCEL_AMBULANCE_REQUEST, + 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> checkPatientERAdvanceBalance() async { + Map mapDevice = {"ClinicID": 10}; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + CHECK_PATIENT_ER_ADVANCE_BALANCE, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final bool patientHasERBalance = response['BalanceAmount'] > 0; + print(patientHasERBalance); + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: patientHasERBalance, + ); + } 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>> checkPatientERPaymentInformation({int? projectID}) async { + Map mapDevice = {"ClinicID": 10, "ProjectID": projectID ?? 0}; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_ER_ONLINE_PAYMENT_DETAILS, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final erOnlineCheckInPaymentDetailsResponse = EROnlineCheckInPaymentDetailsResponse.fromJson(response["ResponsePatientShare"]); + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: erOnlineCheckInPaymentDetailsResponse, + ); + } 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> createAdvancePaymentForER( + {required int projectID, + required AuthenticatedUser authUser, + required num paymentAmount, + required String paymentMethodName, + required String paymentReference}) async { + Map mapDevice = { + "LanguageID": 1, + "ERAdvanceAmount": { + "ProjectId": projectID, + "PatientId": authUser.patientId, + "ClinicId": 10, + "DepositorName": "${authUser.firstName!} ${authUser.lastName!}", + "MemberId": authUser.patientId, + "NationalityID": authUser.nationalityId, + "PaymentAmount": paymentAmount, + "PaymentDate": DateUtil.convertDateToString(DateTime.now()), + "PaymentMethodName": paymentMethodName, + "PaymentReferenceNumber": paymentReference, + "SourceType": 2 + } + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ER_CREATE_ADVANCE_PAYMENT, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final vidaAdvanceNumber = response['ER_AdvancePaymentResponse']['AdvanceNumber'].toString(); + log(vidaAdvanceNumber); + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: vidaAdvanceNumber, + ); + } 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> addAdvanceNumberRequest( + {required String advanceNumber, required String paymentReference, required String appointmentNo}) async { + Map requestBody = { + "AdvanceNumber": advanceNumber, + "AdvanceNumber_VP": advanceNumber, + "PaymentReferenceNumber": paymentReference, + "AppointmentID": appointmentNo, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ADD_ADVANCE_NUMBER_REQUEST, + body: requestBody, + 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>> getProjectIDFromNFC({required String nfcCode}) async { + Map mapDevice = {"nFC_Code": nfcCode}; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_PROJECT_FROM_NFC, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final projectID = response['GetProjectByNFC'][0]["ProjectID"]; + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: projectID, + ); + } 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>> autoGenerateInvoiceERClinic({required int projectID}) async { + Map mapDevice = { + "ProjectID": projectID, + "ClinicID": "10", + "IsAdvanceAvailable": true, + "MemberID": 102, + "PaymentMethod": "VISA", + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + AUTO_GENERATE_INVOICE_ER, + body: mapDevice, + 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())); + } + } } diff --git a/lib/features/emergency_services/emergency_services_view_model.dart b/lib/features/emergency_services/emergency_services_view_model.dart index 77e5823..9526f16 100644 --- a/lib/features/emergency_services/emergency_services_view_model.dart +++ b/lib/features/emergency_services/emergency_services_view_model.dart @@ -4,34 +4,99 @@ import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart' as GMSMapServices; 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/extensions/string_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/model/resp_model/ProjectAvgERWaitingTime.dart'; -import 'package:hmg_patient_app_new/features/emergency_services/models/resp_models/rrt_procedures_response_model.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/EROnlineCheckInPaymentDetailsResponse.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/PatientERTransportationMethod.dart' + show PatientERTransportationMethod; +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/rrt_procedures_response_model.dart'; +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/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/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/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/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; import 'package:url_launcher/url_launcher.dart'; +import '../location/GeocodeResponse.dart'; +import 'models/ambulance_direction.dart'; + class EmergencyServicesViewModel extends ChangeNotifier { EmergencyServicesRepo emergencyServicesRepo; + MyAppointmentsRepo appointmentRepo; ErrorHandlerService errorHandlerService; + DialogService dialogService; + + Completer? gmsController; + Completer? hmsController; final NavigationService navServices; final LocationUtils? locationUtils; final AppState appState; + TextEditingController searchController = TextEditingController(); + + //loading variables bool isERListLoading = false; + bool isTransportationOptionsLoading = false; + List nearestERList = []; List nearestERFilteredList = []; List RRTProceduresList = []; + List? hospitalList; + List? hmgHospitalList; + List? hmcHospitalList; + List? displayList; + HospitalsModel? selectedHospital; + FacilitySelection selectedFacility = FacilitySelection.ALL; + int hmgCount = 0; + int hmcCount = 0; + bool pickupFromInsideTheLocation = true; + List? appointments; + List? orders = []; + + //ambulance selection data section + List transportationOptions = []; + PatientERTransportationMethod? selectedTransportOption; + AmbulanceCallingPlace callingPlace = AmbulanceCallingPlace.FROM_HOSPITAL; + AmbulanceDirection ambulanceDirection = AmbulanceDirection.ONE_WAY; + late RRTProceduresResponseModel selectedRRTProcedure; + bool patientHasAdvanceERBalance = false; + bool isERBookAppointment = false; + late EROnlineCheckInPaymentDetailsResponse erOnlineCheckInPaymentDetailsResponse; + BottomSheetType bottomSheetType = BottomSheetType.FIXED; setSelectedRRTProcedure(RRTProceduresResponseModel procedure) { @@ -39,19 +104,33 @@ class EmergencyServicesViewModel extends ChangeNotifier { notifyListeners(); } + get isGMSAvailable + { + return appState.isGMSAvailable; + } + EmergencyServicesViewModel({ required this.emergencyServicesRepo, required this.errorHandlerService, required this.navServices, required this.locationUtils, required this.appState, + required this.appointmentRepo, + required this.dialogService, }); - get isGMSAvailable - { - return appState.isGMSAvailable; + void setIsERBookAppointment(bool value) { + isERBookAppointment = value; + notifyListeners(); } + bool get isArabic => appState.isArabic(); + + bool haveAnAppointment = false; + + PatientAppointmentHistoryResponseModel? appointment; + + bool isMyAppointmentsLoading = false; Future getRRTProcedures({Function(dynamic)? onSuccess, Function(String)? onError}) async { RRTProceduresList.clear(); @@ -149,46 +228,619 @@ class EmergencyServicesViewModel extends ChangeNotifier { } GMSMapServices.CameraPosition getGMSLocation() { - return GMSMapServices.CameraPosition( - target: GMSMapServices.LatLng(appState.userLat, appState.userLong), zoom: 18); + return GMSMapServices.CameraPosition(target: GMSMapServices.LatLng(appState.userLat, appState.userLong), zoom: 18); } 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() { - return HMSCameraServices.CameraPosition( - target: HMSCameraServices.LatLng(appState.userLat, appState.userLong),zoom: 18); - + return HMSCameraServices.CameraPosition(target: HMSCameraServices.LatLng(appState.userLat, appState.userLong), zoom: 18); } 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() { - locationUtils!.getLocation( - isShowConfirmDialog: true, - onSuccess: (position) { - updateBottomSheetState(BottomSheetType.FIXED); - navServices.push( - CustomPageRoute( - page: CallAmbulancePage(), direction: AxisDirection.down - ), - ); + placeValueInController(); + getProjects(); + flushData(); + selectedFacility = FacilitySelection.ALL; + + print("the app state is ${appState.isAuthenticated}"); + if (appState.isAuthenticated) { + locationUtils!.getLocation( + isShowConfirmDialog: true, + onSuccess: (position) { + updateBottomSheetState(BottomSheetType.FIXED); + navServices.push( + CustomPageRoute( + page: CallAmbulancePage(), direction: AxisDirection.down), + ); + }); + } else { + dialogService.showErrorBottomSheet( + message: "You Need To Login First To Continue".needTranslation, + onOkPressed: () { + navServices.pop(); + navServices.pushAndReplace( + AppRoutes.loginScreen + ); + }); + } + } - }); + void navigateToEROnlineCheckIn() { + navServices.push( + CustomPageRoute(page: ErOnlineCheckinHome()), + ); + } + + void navigateToEROnlineCheckInPaymentPage() { + navServices.push( + CustomPageRoute(page: ErOnlineCheckinPaymentDetailsPage()), + ); } void updateBottomSheetState(BottomSheetType sheetType) { + if (sheetType == BottomSheetType.EXPANDED && selectedHospital == null) { + dialogService.showErrorBottomSheet(message: "Kindly Select Hospital".needTranslation); + return; + } bottomSheetType = sheetType; + if (sheetType == BottomSheetType.EXPANDED) { + clearAppointmentData(); + } notifyListeners(); } 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) { + patientHasAdvanceERBalance = false; + isERBookAppointment = true; + if (onSuccess != null) { + onSuccess(failure.message); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + patientHasAdvanceERBalance = false; + isERBookAppointment = true; + } else if (apiResponse.messageStatus == 1) { + patientHasAdvanceERBalance = apiResponse.data; + isERBookAppointment = !patientHasAdvanceERBalance; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future getPatientERPaymentInformation({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await emergencyServicesRepo.checkPatientERPaymentInformation(projectID: selectedHospital!.iD); + + result.fold( + (failure) { + if (onError != null) { + onError(failure.message); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + } else if (apiResponse.messageStatus == 1) { + erOnlineCheckInPaymentDetailsResponse = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + 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!, + paymentMethodName: paymentMethodName, + paymentReference: paymentReference); + + result.fold( + (failure) { + if (onError != null) { + onError(failure.message); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + } else if (apiResponse.messageStatus == 1) { + // erOnlineCheckInPaymentDetailsResponse = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data); + } + } + }, + ); + } + + 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); + + 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); + } + } + }, + ); + } + + Future getProjectIDFromNFC({required String nfcCode, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await emergencyServicesRepo.getProjectIDFromNFC(nfcCode: nfcCode); + result.fold( + // (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) { + if (onError != null) { + onError(failure.message); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + if (onError != null) { + onError(apiResponse.errorMessage!); + } + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data); + } + } + }, + ); + } + + Future autoGenerateInvoiceERClinic({required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await emergencyServicesRepo.autoGenerateInvoiceERClinic(projectID: projectID); + + result.fold( + // (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) { + if (onError != null) { + onError(failure.message); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + if (onError != null) { + onError(apiResponse.data["InvoiceResponse"]["Message"]); + } + } else if (apiResponse.messageStatus == 1) { + patientHasAdvanceERBalance = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data); + } + } + }, + ); + } + + Future getTransportationOption() async { + //handle the cache if the data is present then dont fetch it in the authenticated lifecycle + + print("the app state is ${appState.isAuthenticated}"); + if (appState.isAuthenticated == false) { + dialogService.showErrorBottomSheet( + message: "You Need To Login First To Continue".needTranslation, + onOkPressed: () { + navServices.pop(); + print("inside the ok button"); + getIt().onLoginPressed(); + }); + return; + } + + if (transportationOptions.isNotEmpty) return; + + int? id = appState.getAuthenticatedUser()?.patientId; + LoaderBottomSheet.showLoader( + loadingText: "Getting Ambulance Transport Option".needTranslation); + + notifyListeners(); + var response = await emergencyServicesRepo.getTransportationMethods(id: id); + + response.fold( + (failure) async { + LoaderBottomSheet.hideLoader(); + }, + (apiResponse) { + transportationOptions = apiResponse.data!; + LoaderBottomSheet.hideLoader(); + notifyListeners(); + }, + ); + } + + Future getTransportationMethods() async { + int? id = appState.getAuthenticatedUser()?.patientId; + LoaderBottomSheet.showLoader( + loadingText: "Getting Ambulance Transport Option".needTranslation); + + notifyListeners(); + var response = await emergencyServicesRepo.getTransportationMethods(id: id); + + response.fold( + (failure) async { + LoaderBottomSheet.hideLoader(); + }, + (apiResponse) { + transportationOptions = apiResponse.data!; + LoaderBottomSheet.hideLoader(); + notifyListeners(); + }, + ); + } + + void setTransportationOption(PatientERTransportationMethod item) { + selectedTransportOption = item; + } + + void updateCallingPlace(AmbulanceCallingPlace? value) { + callingPlace = value!; + notifyListeners(); + } + + void updateDirection(AmbulanceDirection? value) { + ambulanceDirection = value!; + notifyListeners(); + } + + void placeValueInController() { + if (isGMSAvailable) { + gmsController = Completer(); + } else { + hmsController = Completer(); + } + } + + void moveController(Location location) { + if (isGMSAvailable) { + gmsController?.future.then((controller) { + controller.animateCamera( + GMSMapServices.CameraUpdate.newCameraPosition( + GMSMapServices.CameraPosition( + target: GMSMapServices.LatLng(location.lat, location.lng), + zoom: 18, + ), + ), + ); + }); + } else { + hmsController?.future.then((controller) { + controller.animateCamera( + HMSCameraServices.CameraUpdate.newCameraPosition( + HMSCameraServices.CameraPosition( + target: HMSCameraServices.LatLng(location.lat, location.lng), + zoom: 18, + ), + ), + ); + }); + } + } + + void moveToCurrentLocation() { + moveController(Location(lat: appState.userLat, lng: appState.userLong)); + } + + FutureOr getProjects() async { + // if (hospitalList.isNotEmpty) return; + var response = await emergencyServicesRepo.getProjectList(); + + response.fold( + (failure) async {}, + (apiResponse) async { + List? data = apiResponse.data; + if (data == null) return; + hospitalList = data; + hmgHospitalList = data.where((e) => e.isHMC == false).toList(); + hmcHospitalList = data.where((e) => e.isHMC == true).toList(); + hmgCount = hmgHospitalList?.length ?? 0; + hmcCount = hmcHospitalList?.length ?? 0; + getDisplayList(); + notifyListeners(); + }, + ); + } + + setSelectedFacility(FacilitySelection selection) { + selectedFacility = selection; notifyListeners(); + } + searchHospitals(String query) { + if (query.isEmpty) { + getDisplayList(); + return; + } + List? sourceList; + switch (selectedFacility) { + case FacilitySelection.ALL: + sourceList = hospitalList; + break; + case FacilitySelection.HMG: + sourceList = hmgHospitalList; + break; + case FacilitySelection.HMC: + sourceList = hmcHospitalList; + break; + } + displayList = sourceList + ?.where((hospital) => + hospital.name != null && + hospital.name!.toLowerCase().contains(query.toLowerCase())) + .toList(); + notifyListeners(); + } + + getDisplayList() { + switch (selectedFacility) { + case FacilitySelection.ALL: + displayList = hospitalList; + break; + case FacilitySelection.HMG: + displayList = hmgHospitalList; + break; + case FacilitySelection.HMC: + displayList = hmcHospitalList; + break; + } + notifyListeners(); + } + + + void setSelectedHospital(HospitalsModel? hospital) { + selectedHospital = hospital; + notifyListeners(); + } + + String? getSelectedHospitalName() { + return selectedHospital?.getName(isArabic); + } + + void updateThePickupPlaceFromLocation(bool value) { + pickupFromInsideTheLocation = value; + notifyListeners(); + } + + void flushData() { + selectedHospital = null; + displayList = hospitalList; + selectedFacility = FacilitySelection.ALL; + pickupFromInsideTheLocation = false; + haveAnAppointment = false; + appointment = null; + notifyListeners(); + } + + void clearAppointmentData() { + haveAnAppointment = false; + appointment = null; + notifyListeners(); + } + + void flushPickupInformation() { + ambulanceDirection = AmbulanceDirection.ONE_WAY; + callingPlace = AmbulanceCallingPlace.FROM_HOSPITAL; + notifyListeners(); + } + + num getTotalPrice() { + print("the ambulance direction is $ambulanceDirection"); + switch (ambulanceDirection) { + case AmbulanceDirection.ONE_WAY: + return selectedTransportOption?.priceTotal ?? 0; + case AmbulanceDirection.TWO_WAY: + return (selectedTransportOption?.priceTotal ?? 0) * 2; + } + } + + Future updateAppointment(bool value) async { + + if (value) { + await getAppointments(); + } else { + clearAppointmentData(); + } + if(appointments?.isNotEmpty == true) { + haveAnAppointment = value; + } + notifyListeners(); + } + + Future getAppointments() async { + LoaderBottomSheet.showLoader(loadingText: "Fetching Appointment".needTranslation); + var result = await appointmentRepo.getPatientAppointments(isActiveAppointment: true, isArrivedAppointments: false); + LoaderBottomSheet.hideLoader(); + + result.fold((failure) { + haveAnAppointment = false; + notifyListeners(); + errorHandlerService.handleError(failure: failure); + }, (response) { + appointments = response.data; + if (appointments?.isEmpty == true) { + haveAnAppointment = false; + dialogService.showErrorBottomSheet(message: "Unable to Fetch Appointment"); + } + notifyListeners(); + }); + } + + void setSelectedAppointment(PatientAppointmentHistoryResponseModel selectedAppointment) { + appointment = selectedAppointment; + notifyListeners(); + } + + void checkHasAppointment() { + if (appointment == null) { + haveAnAppointment = false; + } + notifyListeners(); + } + + FutureOr submitAmbulanceRequest(GeocodeResult? result, PlaceDetails? place, PlacePrediction? placePrediction) async { + PatientER_RC request = createRequest(result, place, placePrediction); + navServices.push(CustomPageRoute(page: RequestingServicesPage())); + + var response = await emergencyServicesRepo.submitAmbulanceRequest(request); + response.fold((failure) { + navServices.pushAndRemoveUntil( + CustomPageRoute( + page: TrackingScreen( + state: OrderTrackingState.failed, + )), + ModalRoute.withName("/EmergencyServicesPage")); + }, (success) { + getTransportationOrders(shouldNavigateToTrackingScreen: true); + }); + } + + Future getTransportationOrders({bool shouldNavigateToTrackingScreen = false, bool showLoader = false}) async { + if(shouldNavigateToTrackingScreen == false && showLoader ) { + LoaderBottomSheet.showLoader(loadingText: "Fetching Orders"); + } + int? id = appState.getAuthenticatedUser()?.patientId; + + var response = await emergencyServicesRepo.getTransportationOrders(id: id); + if(shouldNavigateToTrackingScreen == false && showLoader ) { + LoaderBottomSheet.hideLoader();} + response.fold( + (failure) async { + if (shouldNavigateToTrackingScreen) { + navServices.pushAndRemoveUntil(CustomPageRoute(page: TrackingScreen(state: OrderTrackingState.waitingForCall)), ModalRoute.withName("/EmergencyServicesPage")); + } + }, + (apiResponse) { + if (shouldNavigateToTrackingScreen) { + navServices.pushAndRemoveUntil( + CustomPageRoute( + page: TrackingScreen( + state: OrderTrackingState.waitingForCall, + order: apiResponse.data?.firstWhere((element) => element.statusId == 1), + )), + ModalRoute.withName("/EmergencyServicesPage")); + } + + orders = apiResponse.data; + notifyListeners(); + }, + ); + } + + PatientER_RC createRequest(GeocodeResult? result, PlaceDetails? place, PlacePrediction? placePrediction) { + PatientER_RC request = PatientER_RC(); + TransportationDetails transportationDetails = TransportationDetails(); + transportationDetails.requesterIsOutSA = false; + transportationDetails.pickupSpot = pickupFromInsideTheLocation ? 1 : 0; + + transportationDetails.tripType = ambulanceDirection == AmbulanceDirection.ONE_WAY ? 0 : 1; + transportationDetails.ambulate = (transportationOptions.indexOf(selectedTransportOption!) + 1); + transportationDetails.transportationType = selectedTransportOption?.iD.toString() ?? ""; + request.patientERTransportationMethod = selectedTransportOption; + transportationDetails.pickupUrgency = 1; + switch (callingPlace) { + case AmbulanceCallingPlace.FROM_HOSPITAL: + transportationDetails.dropoffLatitude = selectedHospital!.latitude; + transportationDetails.dropoffLongitude = selectedHospital!.longitude; + transportationDetails.dropoffLocationName = selectedHospital!.name; + transportationDetails.pickupLatitude = ((result?.geometry.location.lat) ?? place?.lat)?.toStringAsFixed(6); + transportationDetails.pickupLongitude = ((result?.geometry.location.lng) ?? place?.lng)?.toStringAsFixed(6); + transportationDetails.pickupLocationName = result?.formattedAddress ?? placePrediction?.description; + transportationDetails.direction = 1; + break; + case AmbulanceCallingPlace.TO_HOSPITAL: + transportationDetails.pickupLatitude = selectedHospital!.latitude; + transportationDetails.pickupLongitude = selectedHospital!.longitude; + transportationDetails.pickupLocationName = selectedHospital!.name; + transportationDetails.dropoffLatitude = ((result?.geometry.location.lat) ?? place?.lat)?.toStringAsFixed(6); + transportationDetails.dropoffLongitude = ((result?.geometry.location.lng) ?? place?.lng)?.toStringAsFixed(6); + transportationDetails.dropoffLocationName = result?.formattedAddress ?? placePrediction?.description; + transportationDetails.direction = 0; + break; + } + AuthenticatedUser? user = appState.getAuthenticatedUser(); + if (user == null) throw Exception("Authentication Required to Continue"); + + request.patientIdentificationID = user.patientIdentificationNo; + request.projectID = user.projectId; + request.patientID = user.patientId; + transportationDetails.requesterIsOutSA = false; + transportationDetails.requesterMobileNo = user.mobileNumber; + transportationDetails.requesterFileNo = user.patientId; + transportationDetails.pickupDateTime = DateUtil.convertDateToStringLocation(DateTime.now()); + if (haveAnAppointment) { + transportationDetails.appointmentNo = appointment!.appointmentNo.toString(); + transportationDetails.appointmentClinicName = appointment!.clinicName; + transportationDetails.appointmentDoctorName = appointment!.doctorNameObj; + transportationDetails.appointmentBranch = appointment!.projectName; + transportationDetails.appointmentTime = appointment!.appointmentDate; + transportationDetails.haveAppointment = 1; + transportationDetails.dropoffLatitude = appointment!.latitude; + transportationDetails.dropoffLongitude = appointment!.longitude; + transportationDetails.dropoffLocationName = appointment!.projectName; + } + + transportationDetails.pickupSpot = pickupFromInsideTheLocation ? 1 : 0; + transportationDetails.ambulate = 3; + transportationDetails.ambulateTitle = "none"; + transportationDetails.notes = ""; + request.transportationDetails = transportationDetails; + + return request; + } + + void clearSearchText() { + searchController.clear(); + } + + Future cancelOrder(AmbulanceRequestOrdersModel? order, {bool shouldPop = false}) async { + dialogService.showCommonBottomSheetWithoutH( + message: "Do you want to cancel the order".needTranslation, + onOkPressed: () async { + navServices.pop(); + LoaderBottomSheet.showLoader(loadingText: "Cancelling Order".needTranslation); + var response = await emergencyServicesRepo.cancelOrder(order?.iD, appState.getAuthenticatedUser()?.patientId ?? 0); + LoaderBottomSheet.hideLoader(); + response.fold((failure) => errorHandlerService.handleError(failure: failure), (success) { + getTransportationOrders(); + if (shouldPop) navServices.pop(); + }); + }, + onCancelPressed: () { + navServices.pop(); + }); } } diff --git a/lib/features/emergency_services/models/AmbulanceCallingPlace.dart b/lib/features/emergency_services/models/AmbulanceCallingPlace.dart new file mode 100644 index 0000000..cf648f7 --- /dev/null +++ b/lib/features/emergency_services/models/AmbulanceCallingPlace.dart @@ -0,0 +1,4 @@ +enum AmbulanceCallingPlace{ + FROM_HOSPITAL, + TO_HOSPITAL +} \ No newline at end of file diff --git a/lib/features/emergency_services/models/ambulance_direction.dart b/lib/features/emergency_services/models/ambulance_direction.dart new file mode 100644 index 0000000..d301921 --- /dev/null +++ b/lib/features/emergency_services/models/ambulance_direction.dart @@ -0,0 +1,3 @@ +enum AmbulanceDirection{ + ONE_WAY, TWO_WAY +} \ No newline at end of file diff --git a/lib/features/emergency_services/models/request_model/PatientER_RC.dart b/lib/features/emergency_services/models/request_model/PatientER_RC.dart new file mode 100644 index 0000000..9c3bbdc --- /dev/null +++ b/lib/features/emergency_services/models/request_model/PatientER_RC.dart @@ -0,0 +1,205 @@ + +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/PatientERTransportationMethod.dart' show PatientERTransportationMethod; + +class PatientER_RC { + double? versionID; + int? channel; + int? languageID; + String? iPAdress; + String? generalid; + bool? patientOutSA; + String? sessionID; + bool? isDentalAllowedBackend; + int? deviceTypeID; + int? patientID; + String? tokenID; + int? patientTypeID; + int? patientType; + int? orderServiceID; + String? patientIdentificationID; + dynamic patientOutSa; + int? projectID; + int? lineItemNo; + TransportationDetails? transportationDetails; + PatientERTransportationMethod? patientERTransportationMethod; + + PatientER_RC( + {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.orderServiceID, + this.patientIdentificationID, + this.patientOutSa, + this.projectID, + this.lineItemNo, + this.transportationDetails }); + + PatientER_RC.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']; + orderServiceID = json['OrderServiceID']; + patientIdentificationID = json['PatientIdentificationID']; + patientOutSa = json['patientOutSa']; + projectID = json['projectID']; + lineItemNo = json['lineItemNo']; + transportationDetails = json['transportationDetails'] != null + ? new TransportationDetails.fromJson(json['transportationDetails']) + : null; + } + + 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['PatientOutSA'] = this.patientOutSA; + data['SessionID'] = this.sessionID; + data['isDentalAllowedBackend'] = this.isDentalAllowedBackend; + data['DeviceTypeID'] = this.deviceTypeID; + data['PatientID'] = this.patientID; + data['TokenID'] = this.tokenID; + data['PatientTypeID'] = this.patientTypeID; + data['PatientType'] = this.patientType; + data['OrderServiceID'] = this.orderServiceID; + data['PatientIdentificationID'] = this.patientIdentificationID; + data['patientOutSa'] = this.patientOutSa; + data['projectID'] = this.projectID; + data['lineItemNo'] = this.lineItemNo; + if (this.transportationDetails != null) { + data['transportationDetails'] = this.transportationDetails!.toJson(); + } + return data; + } +} + +class TransportationDetails { + int? direction; + int? haveAppointment; + int? tripType; + int? pickupUrgency; + int? pickupSpot; + String? pickupDateTime; + String? transportationType; + int? ambulate; + String? ambulateTitle; + String? notes; + int? requesterFileNo; + String? requesterMobileNo; + bool? requesterIsOutSA; + String? pickupLocationName; + String? dropoffLocationName; + String? pickupLatitude; + String? pickupLongitude; + String? dropoffLatitude; + String? dropoffLongitude; + String? appointmentNo; + String? appointmentClinicName; + String? appointmentDoctorName; + String? appointmentBranch; + String? appointmentTime; + + TransportationDetails( + {this.direction, + this.haveAppointment = 0, + this.tripType, + this.pickupUrgency, + this.pickupSpot, + this.pickupDateTime, + this.transportationType, + this.ambulate, + this.ambulateTitle, + this.notes, + this.requesterFileNo, + this.requesterMobileNo, + this.requesterIsOutSA, + this.pickupLocationName, + this.dropoffLocationName, + this.pickupLatitude, + this.pickupLongitude, + this.dropoffLatitude, + this.dropoffLongitude, + this.appointmentNo = "0", + this.appointmentClinicName = "", + this.appointmentDoctorName = "", + this.appointmentBranch = "", + this.appointmentTime = ""}); + + TransportationDetails.fromJson(Map json) { + direction = json['direction']; + haveAppointment = json['haveAppointment']; + tripType = json['tripType']; + pickupUrgency = json['pickupUrgency']; + pickupSpot = json['pickupSpot']; + pickupDateTime = json['pickupDateTime']; + transportationType = json['transportationType']; + ambulate = json['ambulate']; + ambulateTitle = ""; + notes = json['notes']; + requesterFileNo = json['requesterFileNo']; + requesterMobileNo = json['requesterMobileNo']; + requesterIsOutSA = json['requesterIsOutSA']; + pickupLocationName = json['pickupLocationName']; + dropoffLocationName = json['dropoffLocationName']; + pickupLatitude = json['pickup_Latitude']; + pickupLongitude = json['pickup_Longitude']; + dropoffLatitude = json['dropoff_Latitude']; + dropoffLongitude = json['dropoff_Longitude']; + appointmentNo = json['appointmentNo']; + appointmentClinicName = json['appointmentClinicName']; + appointmentDoctorName = json['appointmentDoctorName']; + appointmentBranch = json['appointmentBranch']; + appointmentTime = json['appointmentTime']; + } + + Map toJson() { + final Map data = new Map(); + data['direction'] = this.direction; + data['haveAppointment'] = this.haveAppointment; + data['tripType'] = this.tripType; + data['pickupUrgency'] = this.pickupUrgency; + data['pickupSpot'] = this.pickupSpot; + data['pickupDateTime'] = this.pickupDateTime; + data['transportationType'] = this.transportationType; + data['ambulate'] = this.ambulate; + data['ambulateTitle'] = this.ambulateTitle; + data['notes'] = this.notes; + data['requesterFileNo'] = this.requesterFileNo; + data['requesterMobileNo'] = this.requesterMobileNo; + data['requesterIsOutSA'] = this.requesterIsOutSA; + data['pickupLocationName'] = this.pickupLocationName; + data['dropoffLocationName'] = this.dropoffLocationName; + data['pickup_Latitude'] = this.pickupLatitude; + data['pickup_Longitude'] = this.pickupLongitude; + data['dropoff_Latitude'] = this.dropoffLatitude; + data['dropoff_Longitude'] = this.dropoffLongitude; + data['appointmentNo'] = this.appointmentNo; + data['appointmentClinicName'] = this.appointmentClinicName; + data['appointmentDoctorName'] = this.appointmentDoctorName; + data['appointmentBranch'] = this.appointmentBranch; + data['appointmentTime'] = this.appointmentTime; + return data; + } +} diff --git a/lib/features/emergency_services/models/resp_model/AmbulanceRequestOrdersModel.dart b/lib/features/emergency_services/models/resp_model/AmbulanceRequestOrdersModel.dart new file mode 100644 index 0000000..6993183 --- /dev/null +++ b/lib/features/emergency_services/models/resp_model/AmbulanceRequestOrdersModel.dart @@ -0,0 +1,346 @@ +class AmbulanceRequestOrdersModel { + String? statusText; + num? paymentStatus; + dynamic clientRequestid; + dynamic paymentStatusText; + dynamic projectName; + String? nearestProjectName; + num? paymentAmount; + WFOrder? wFOrder; + String? serviceText; + bool? isSentForApproval; + num? exaCartOrderId; + String? exaCartGUID; + bool? isTimer; + num? timeSeconds; + num? totalPendingSeconds; + num? timeMinute; + num? timeHour; + num? timeTotalSeconds; + num? timeTotalMinute; + num? timeTotalHour; + dynamic approvalStatus; + bool? isActive; + num? clickButton; + dynamic orderHistory; + String? pickupLocation; + String? dropOffLocation; + String? clinicName; + String? doctorName; + String? branch; + String? time; + String? notes; + int? iD; + num? patientId; + num? patientOutSa; + bool? isOutPatient; + num? projectId; + num? nearestProjectId; + dynamic longitude; + dynamic latitude; + dynamic appointmentNo; + dynamic dischargeId; + int? statusId; + num? serviceId; + num? channel; + Orderpayment? orderpayment; + dynamic wforder; + dynamic orderapprovalobj; + String? created; + dynamic createdBy; + dynamic modified; + dynamic modifiedBy; + bool? isDeleted; + + AmbulanceRequestOrdersModel( + {this.statusText, + this.paymentStatus, + this.clientRequestid, + this.paymentStatusText, + this.projectName, + this.nearestProjectName, + this.paymentAmount, + this.wFOrder, + this.serviceText, + this.isSentForApproval, + this.exaCartOrderId, + this.exaCartGUID, + this.isTimer, + this.timeSeconds, + this.totalPendingSeconds, + this.timeMinute, + this.timeHour, + this.timeTotalSeconds, + this.timeTotalMinute, + this.timeTotalHour, + this.approvalStatus, + this.isActive, + this.clickButton, + this.orderHistory, + this.pickupLocation, + this.dropOffLocation, + this.clinicName, + this.doctorName, + this.branch, + this.time, + this.notes, + 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.wforder, + this.orderapprovalobj, + this.created, + this.createdBy, + this.modified, + this.modifiedBy, + this.isDeleted}); + + AmbulanceRequestOrdersModel.fromJson(Map json) { + 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']; + exaCartGUID = json['ExaCart_GUID']; + 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']; + orderHistory = json['OrderHistory']; + pickupLocation = json['PickupLocation']; + dropOffLocation = json['DropOffLocation']; + clinicName = json['clinicName']; + doctorName = json['DoctorName']; + branch = json['Branch']; + time = json['Time']; + notes = json['Notes']; + 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; + wforder = json['wforder']; + orderapprovalobj = json['orderapprovalobj']; + created = json['Created']; + createdBy = json['CreatedBy']; + modified = json['Modified']; + modifiedBy = json['ModifiedBy']; + isDeleted = json['IsDeleted']; + } + + Map toJson() { + final Map data = new Map(); + 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['ExaCart_GUID'] = this.exaCartGUID; + 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['OrderHistory'] = this.orderHistory; + 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; + 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['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; + return data; + } +} + +class WFOrder { + dynamic wfButtonsDTO; + num? iD; + num? orderId; + num? previousStep; + num? nextStep; + num? 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 Orderpayment { + num? iD; + num? orderId; + dynamic clientRequestId; + num? totalAmount; + num? 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; + } +} diff --git a/lib/features/emergency_services/models/resp_model/EROnlineCheckInPaymentDetailsResponse.dart b/lib/features/emergency_services/models/resp_model/EROnlineCheckInPaymentDetailsResponse.dart new file mode 100644 index 0000000..5cac2cc --- /dev/null +++ b/lib/features/emergency_services/models/resp_model/EROnlineCheckInPaymentDetailsResponse.dart @@ -0,0 +1,108 @@ +class EROnlineCheckInPaymentDetailsResponse { + num? cashPrice; + num? cashPriceTax; + num? cashPriceWithTax; + int? companyId; + String? companyName; + num? companyShareWithTax; + dynamic errCode; + int? groupID; + String? insurancePolicyNo; + String? message; + String? patientCardID; + num? patientShare; + num? patientShareWithTax; + num? patientTaxAmount; + int? policyId; + String? policyName; + String? procedureId; + String? procedureName; + dynamic setupID; + int? statusCode; + String? subPolicyNo; + bool? isCash; + bool? isEligible; + bool? isInsured; + + EROnlineCheckInPaymentDetailsResponse( + {this.cashPrice, + this.cashPriceTax, + this.cashPriceWithTax, + this.companyId, + this.companyName, + this.companyShareWithTax, + this.errCode, + this.groupID, + this.insurancePolicyNo, + this.message, + this.patientCardID, + this.patientShare, + this.patientShareWithTax, + this.patientTaxAmount, + this.policyId, + this.policyName, + this.procedureId, + this.procedureName, + this.setupID, + this.statusCode, + this.subPolicyNo, + this.isCash, + this.isEligible, + this.isInsured}); + + EROnlineCheckInPaymentDetailsResponse.fromJson(Map json) { + cashPrice = json['CashPrice']; + cashPriceTax = json['CashPriceTax']; + cashPriceWithTax = json['CashPriceWithTax']; + companyId = json['CompanyId']; + companyName = json['CompanyName']; + companyShareWithTax = json['CompanyShareWithTax']; + errCode = json['ErrCode']; + groupID = json['GroupID']; + insurancePolicyNo = json['InsurancePolicyNo']; + message = json['Message']; + patientCardID = json['PatientCardID']; + patientShare = json['PatientShare']; + patientShareWithTax = json['PatientShareWithTax']; + patientTaxAmount = json['PatientTaxAmount']; + policyId = json['PolicyId']; + policyName = json['PolicyName']; + procedureId = json['ProcedureId']; + procedureName = json['ProcedureName']; + setupID = json['SetupID']; + statusCode = json['StatusCode']; + subPolicyNo = json['SubPolicyNo']; + isCash = json['IsCash']; + isEligible = json['IsEligible']; + isInsured = json['IsInsured']; + } + + Map toJson() { + final Map data = new Map(); + data['CashPrice'] = this.cashPrice; + data['CashPriceTax'] = this.cashPriceTax; + data['CashPriceWithTax'] = this.cashPriceWithTax; + data['CompanyId'] = this.companyId; + data['CompanyName'] = this.companyName; + data['CompanyShareWithTax'] = this.companyShareWithTax; + data['ErrCode'] = this.errCode; + data['GroupID'] = this.groupID; + data['InsurancePolicyNo'] = this.insurancePolicyNo; + data['Message'] = this.message; + data['PatientCardID'] = this.patientCardID; + data['PatientShare'] = this.patientShare; + data['PatientShareWithTax'] = this.patientShareWithTax; + data['PatientTaxAmount'] = this.patientTaxAmount; + data['PolicyId'] = this.policyId; + data['PolicyName'] = this.policyName; + data['ProcedureId'] = this.procedureId; + data['ProcedureName'] = this.procedureName; + data['SetupID'] = this.setupID; + data['StatusCode'] = this.statusCode; + data['SubPolicyNo'] = this.subPolicyNo; + data['IsCash'] = this.isCash; + data['IsEligible'] = this.isEligible; + data['IsInsured'] = this.isInsured; + return data; + } +} diff --git a/lib/features/emergency_services/models/resp_model/PatientERTransportationMethod.dart b/lib/features/emergency_services/models/resp_model/PatientERTransportationMethod.dart new file mode 100644 index 0000000..e4e6c7d --- /dev/null +++ b/lib/features/emergency_services/models/resp_model/PatientERTransportationMethod.dart @@ -0,0 +1,56 @@ +class PatientERTransportationMethod { + int? iD; + String? serviceID; + int? orderServiceID; + String? text; + String? textN; + dynamic price; + dynamic priceVAT; + dynamic priceTotal; + bool? isEnabled; + int? orderId; + int? quantity; + + PatientERTransportationMethod( + {this.iD, + this.serviceID, + this.orderServiceID, + this.text, + this.textN, + this.price, + this.priceVAT, + this.priceTotal, + this.isEnabled, + this.orderId, + this.quantity}); + + PatientERTransportationMethod.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 = new Map(); + 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; + } +} \ No newline at end of file diff --git a/lib/features/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart b/lib/features/emergency_services/models/resp_model/ProjectAvgERWaitingTime.dart similarity index 100% rename from lib/features/emergency_services/model/resp_model/ProjectAvgERWaitingTime.dart rename to lib/features/emergency_services/models/resp_model/ProjectAvgERWaitingTime.dart diff --git a/lib/features/emergency_services/models/resp_models/rrt_procedures_response_model.dart b/lib/features/emergency_services/models/resp_model/rrt_procedures_response_model.dart similarity index 100% rename from lib/features/emergency_services/models/resp_models/rrt_procedures_response_model.dart rename to lib/features/emergency_services/models/resp_model/rrt_procedures_response_model.dart diff --git a/lib/features/location/GeocodeResponse.dart b/lib/features/location/GeocodeResponse.dart new file mode 100644 index 0000000..3e911aa --- /dev/null +++ b/lib/features/location/GeocodeResponse.dart @@ -0,0 +1,80 @@ +class GeocodeResponse { + final List results; + final String status; + + GeocodeResponse({ + required this.results, + required this.status, + }); + + factory GeocodeResponse.fromJson(Map json) { + final resultsList = (json['results'] as List? ?? []) + .map((e) => GeocodeResult.fromJson(e as Map)) + .toList(); + + return GeocodeResponse( + results: resultsList, + status: json['status'] ?? '', + ); + } + @override + String toString() { + return 'GeocodeResponse(status: $status, results: [${results.map((r) => r.toString()).join(', ')}])'; + } +} + +class GeocodeResult { + final String formattedAddress; + final Geometry geometry; + final String placeId; + + GeocodeResult({ + required this.formattedAddress, + required this.geometry, + required this.placeId, + }); + + factory GeocodeResult.fromJson(Map json) { + return GeocodeResult( + formattedAddress: json['formatted_address'] ?? '', + geometry: Geometry.fromJson(json['geometry']), + placeId: json['place_id'] ?? '', + ); + } + @override + String toString() { + return 'GeocodeResult(formattedAddress: $formattedAddress, placeId: $placeId, geometry: ${geometry.toString()})'; + } +} + +class Geometry { + final Location location; + + Geometry({required this.location}); + + factory Geometry.fromJson(Map json) { + return Geometry( + location: Location.fromJson(json['location']), + ); + } + + @override + String toString() => 'Geometry(location: ${location.toString()})'; +} + +class Location { + final double lat; + final double lng; + + Location({required this.lat, required this.lng}); + + factory Location.fromJson(Map json) { + return Location( + lat: (json['lat'] as num).toDouble(), + lng: (json['lng'] as num).toDouble(), + ); + } + + @override + String toString() => 'Location(lat: $lat, lng: $lng)'; +} \ No newline at end of file diff --git a/lib/features/location/PlaceDetails.dart b/lib/features/location/PlaceDetails.dart new file mode 100644 index 0000000..82846ea --- /dev/null +++ b/lib/features/location/PlaceDetails.dart @@ -0,0 +1,14 @@ +class PlaceDetails { + final double lat; + final double lng; + + PlaceDetails({required this.lat, required this.lng}); + + factory PlaceDetails.fromJson(Map json) { + final loc = json['result']['geometry']['location']; + return PlaceDetails( + lat: (loc['lat'] as num).toDouble(), + lng: (loc['lng'] as num).toDouble(), + ); + } +} \ No newline at end of file diff --git a/lib/features/location/PlacePrediction.dart b/lib/features/location/PlacePrediction.dart new file mode 100644 index 0000000..ea68153 --- /dev/null +++ b/lib/features/location/PlacePrediction.dart @@ -0,0 +1,11 @@ +class PlacePrediction { + final String description; + final String placeID; + PlacePrediction({required this.description, required this.placeID}); + factory PlacePrediction.fromJson(Map json) { + return PlacePrediction( + description: json['description'] ?? '', + placeID: json['place_id'] ?? '', + ); + } +} \ No newline at end of file diff --git a/lib/features/location/location_repo.dart b/lib/features/location/location_repo.dart new file mode 100644 index 0000000..46c97ac --- /dev/null +++ b/lib/features/location/location_repo.dart @@ -0,0 +1,153 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.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/location/GeocodeResponse.dart'; +import 'package:hmg_patient_app_new/features/location/PlaceDetails.dart'; + +import '../../core/api_consts.dart'; +import 'PlacePrediction.dart'; + +abstract class LocationRepo { + Future>>> + getPlacePredictionsAsInput(String input); + + Future>> + getPlaceDetailsOfSelectedPrediction(String placeId); + + Future>> + getGeoCodeFromLatLng(double lat, double lng); +} + +class LocationRepoImpl implements LocationRepo { + final ApiClient apiClient; + + LocationRepoImpl({required this.apiClient}); + + @override + Future>>> getPlacePredictionsAsInput( + String input) async { + final url = Uri.parse( + 'https://maps.googleapis.com/maps/api/place/autocomplete/json?input=$input®ion=SA&key=$GOOGLE_API_KEY', + ); + + GenericApiModel>? apiResponse; + Failure? failure; + + try { + await apiClient.get( + url.toString(), + isExternal: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['predictions']; + final predictionsList = list + .map((item) => + PlacePrediction.fromJson(item as Map)) + .toList() + .cast(); + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: predictionsList, + ); + } 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>> getGeoCodeFromLatLng(double lat, double lng) async { + final url = Uri.parse( + 'https://maps.googleapis.com/maps/api/geocode/json?latlng=$lat,$lng&key=$GOOGLE_API_KEY', + ); + + + GenericApiModel? apiResponse; + Failure? failure; + + try { + await apiClient.get( + url.toString(), + isExternal: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final predictionsList = GeocodeResponse.fromJson(response); + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: predictionsList, + ); + } 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>> getPlaceDetailsOfSelectedPrediction(String placeId) async { + + + final url = Uri.parse( + 'https://maps.googleapis.com/maps/api/place/details/json' + '?place_id=$placeId&fields=geometry&key=$GOOGLE_API_KEY', + ); + + GenericApiModel? apiResponse; + Failure? failure; + + try { + await apiClient.get( + url.toString(), + isExternal: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final predictionsList = PlaceDetails.fromJson(response); + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: predictionsList, + ); + } 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/location/location_view_model.dart b/lib/features/location/location_view_model.dart new file mode 100644 index 0000000..e2e0cc5 --- /dev/null +++ b/lib/features/location/location_view_model.dart @@ -0,0 +1,116 @@ +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/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'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; +import 'package:huawei_map/huawei_map.dart' as HMSCameraServices; +import 'package:google_maps_flutter/google_maps_flutter.dart' as GMSMapServices; + + + +import 'PlacePrediction.dart'; + +class LocationViewModel extends ChangeNotifier { + final LocationRepo locationRepo; + final ErrorHandlerService errorHandlerService; + + LocationViewModel({required this.locationRepo, required this.errorHandlerService}); + + List predictions = []; + PlacePrediction? selectedPrediction; + bool isPredictionLoading = false; + GeocodeResponse? geocodeResponse; + PlaceDetails? placeDetails; + + Location? mapCapturedLocation; + + FutureOr getPlacesPrediction(String input) async { + predictions = []; + isPredictionLoading= true; + final result = await locationRepo.getPlacePredictionsAsInput(input); + result.fold( + (failure) { + errorHandlerService.handleError(failure: failure); + }, + (apiModel) { + predictions = apiModel.data??[]; + }, + ); + isPredictionLoading = false; + notifyListeners(); + } + + FutureOr getPlaceEncodedData(double? lat, double? lng) async { + geocodeResponse = null; + final result = await locationRepo.getGeoCodeFromLatLng(lat!, lng!); + result.fold( + (failure) { + errorHandlerService.handleError(failure: failure); + }, + (apiModel) { + print("Geocode Response: ${apiModel.data}"); + geocodeResponse = apiModel.data; + }, + ); + notifyListeners(); + } + + FutureOr getPlaceDetails(String placeID) async { + placeDetails = null; + final result = await locationRepo.getPlaceDetailsOfSelectedPrediction(placeID); + result.fold( + (failure) { + errorHandlerService.handleError(failure: failure); + }, + (apiModel) { + placeDetails = apiModel.data; + }, + ); + notifyListeners(); + } + + handleGMSMapCameraMoved(GMSMapServices.CameraPosition value) { + mapCapturedLocation = Location(lat: value.target.latitude, lng: value.target.longitude); + + } + + handleHMSMapCameraMoved(HMSCameraServices.CameraPosition value) { + mapCapturedLocation = Location(lat: value.target.lat, lng: value.target.lng); + } + + handleOnCameraIdle(){ + if(mapCapturedLocation != null) { + getPlaceEncodedData(mapCapturedLocation!.lat, mapCapturedLocation!.lng); + } + } + + void updateSearchQuery(String? value) { + if(value == null || value.isEmpty){ + predictions = []; + return; + } + + getPlacesPrediction(value); + } + + void flushSearchPredictions() { + predictions = []; + mapCapturedLocation= null; + placeDetails= null; + geocodeResponse= null; + selectedPrediction= null; + + notifyListeners(); + } + + FutureOr selectPlacePrediction(PlacePrediction placePrediction) async{ + selectedPrediction= placePrediction; + await getPlaceDetails(placePrediction.placeID); + } + + +} \ 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 6c6354a..4a0ffab 100644 --- a/lib/features/my_appointments/appointment_via_region_viewmodel.dart +++ b/lib/features/my_appointments/appointment_via_region_viewmodel.dart @@ -122,7 +122,7 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier { } void handleLastStepForDentalAndLaser() { - //todo handle the routing here + //todo_section handle the routing here navigationService.pop(); navigationService.push( CustomPageRoute( diff --git a/lib/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart b/lib/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart new file mode 100644 index 0000000..3b53214 --- /dev/null +++ b/lib/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart @@ -0,0 +1,92 @@ +class AskDocRequestType { + String? setupID; + int? parameterGroup; + int? parameterType; + int? parameterCode; + String? description; + String? descriptionN; + String? alias; + String? aliasN; + String? prefix; + String? suffix; + String? isColorCodingRequired; + String? backColor; + String? foreColor; + bool? isBuiltIn; + bool? isActive; + int? createdBy; + String? createdOn; + String? editedBy; + String? editedOn; + String? rowVer; + + AskDocRequestType( + {this.setupID, + this.parameterGroup, + this.parameterType, + this.parameterCode, + this.description, + this.descriptionN, + this.alias, + this.aliasN, + this.prefix, + this.suffix, + this.isColorCodingRequired, + this.backColor, + this.foreColor, + this.isBuiltIn, + this.isActive, + this.createdBy, + this.createdOn, + this.editedBy, + this.editedOn, + this.rowVer}); + + AskDocRequestType.fromJson(Map json) { + setupID = json['SetupID']; + parameterGroup = json['ParameterGroup']; + parameterType = json['ParameterType']; + parameterCode = json['ParameterCode']; + description = json['Description']; + descriptionN = json['DescriptionN']; + alias = json['Alias']; + aliasN = json['AliasN']; + prefix = json['Prefix']; + suffix = json['Suffix']; + isColorCodingRequired = json['IsColorCodingRequired']; + backColor = json['BackColor']; + foreColor = json['ForeColor']; + isBuiltIn = json['IsBuiltIn']; + isActive = json['IsActive']; + createdBy = json['CreatedBy']; + createdOn = json['CreatedOn']; + editedBy = json['EditedBy']; + editedOn = json['EditedOn']; + rowVer = json['RowVer']; + } + + Map toJson() { + final Map data = new Map(); + data['SetupID'] = this.setupID; + data['ParameterGroup'] = this.parameterGroup; + data['ParameterType'] = this.parameterType; + data['ParameterCode'] = this.parameterCode; + data['Description'] = this.description; + data['DescriptionN'] = this.descriptionN; + data['Alias'] = this.alias; + data['AliasN'] = this.aliasN; + data['Prefix'] = this.prefix; + data['Suffix'] = this.suffix; + data['IsColorCodingRequired'] = this.isColorCodingRequired; + data['BackColor'] = this.backColor; + data['ForeColor'] = this.foreColor; + data['IsBuiltIn'] = this.isBuiltIn; + data['IsActive'] = this.isActive; + data['CreatedBy'] = this.createdBy; + data['CreatedOn'] = this.createdOn; + data['EditedBy'] = this.editedBy; + data['EditedOn'] = this.editedOn; + data['RowVer'] = this.rowVer; + return data; + } +} 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 342fcea..9a211d0 100644 --- a/lib/features/my_appointments/models/resp_models/hospital_model.dart +++ b/lib/features/my_appointments/models/resp_models/hospital_model.dart @@ -101,4 +101,6 @@ class HospitalsModel { data['UsingInDoctorApp'] = this.usingInDoctorApp; return data; } + + } diff --git a/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart b/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart index 4d8eb9c..e1ae67d 100644 --- a/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart +++ b/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart @@ -55,8 +55,8 @@ class PatientAppointmentHistoryResponseModel { bool? isMedicalReportRequested; bool? isOnlineCheckedIN; String? latitude; - dynamic listHISGetContactLensPerscription; - dynamic listHISGetGlassPerscription; + List? listHISGetContactLensPrescription; + List? listHISGetGlassPrescription; String? longitude; dynamic nextAction; dynamic noOfPatientsRate; @@ -130,8 +130,8 @@ class PatientAppointmentHistoryResponseModel { this.isMedicalReportRequested, this.isOnlineCheckedIN, this.latitude, - this.listHISGetContactLensPerscription, - this.listHISGetGlassPerscription, + this.listHISGetContactLensPrescription, + this.listHISGetGlassPrescription, this.longitude, this.nextAction, this.noOfPatientsRate, @@ -207,8 +207,18 @@ class PatientAppointmentHistoryResponseModel { isMedicalReportRequested = json['IsMedicalReportRequested']; isOnlineCheckedIN = json['IsOnlineCheckedIN']; latitude = json['Latitude']; - listHISGetContactLensPerscription = json['List_HIS_GetContactLensPerscription']; - listHISGetGlassPerscription = json['List_HIS_GetGlassPerscription']; + if (json['List_HIS_GetContactLensPerscription'] != null) { + listHISGetContactLensPrescription = []; + json['List_HIS_GetContactLensPerscription'].forEach((v) { + listHISGetContactLensPrescription!.add(ListHISGetContactLensPrescription.fromJson(v)); + }); + } + if (json['List_HIS_GetGlassPerscription'] != null) { + listHISGetGlassPrescription = []; + json['List_HIS_GetGlassPerscription'].forEach((v) { + listHISGetGlassPrescription!.add(ListHISGetGlassPrescription.fromJson(v)); + }); + } longitude = json['Longitude']; nextAction = json['NextAction']; noOfPatientsRate = json['NoOfPatientsRate']; @@ -285,8 +295,8 @@ class PatientAppointmentHistoryResponseModel { data['IsMedicalReportRequested'] = this.isMedicalReportRequested; data['IsOnlineCheckedIN'] = this.isOnlineCheckedIN; data['Latitude'] = this.latitude; - data['List_HIS_GetContactLensPerscription'] = this.listHISGetContactLensPerscription; - data['List_HIS_GetGlassPerscription'] = this.listHISGetGlassPerscription; + // data['List_HIS_GetContactLensPerscription'] = this.listHISGetContactLensPerscription; + // data['List_HIS_GetGlassPerscription'] = this.listHISGetGlassPerscription; data['Longitude'] = this.longitude; data['NextAction'] = this.nextAction; data['NoOfPatientsRate'] = this.noOfPatientsRate; @@ -301,6 +311,196 @@ class PatientAppointmentHistoryResponseModel { } } +class ListHISGetContactLensPrescription { + String? setupId; + int? projectId; + int? patientType; + int? patientId; + int? encounterType; + int? encounterNo; + int? oDOS; + dynamic brand; + dynamic baseCurve; + dynamic power; + dynamic diameter; + dynamic oZ; + dynamic cT; + dynamic blend; + String? remarks; + int? status; + bool? isActive; + String? createdOn; + + ListHISGetContactLensPrescription( + {this.setupId, + this.projectId, + this.patientType, + this.patientId, + this.encounterType, + this.encounterNo, + this.oDOS, + this.brand, + this.baseCurve, + this.power, + this.diameter, + this.oZ, + this.cT, + this.blend, + this.remarks, + this.status, + this.isActive, + this.createdOn}); + + ListHISGetContactLensPrescription.fromJson(Map json) { + setupId = json['SetupId']; + projectId = json['ProjectId']; + patientType = json['PatientType']; + patientId = json['PatientId']; + encounterType = json['EncounterType']; + encounterNo = json['EncounterNo']; + oDOS = json['OD_OS']; + brand = json['Brand']; + baseCurve = json['BaseCurve']; + power = json['Power']; + diameter = json['Diameter']; + oZ = json['OZ']; + cT = json['CT']; + blend = json['Blend']; + remarks = json['Remarks']; + status = json['Status']; + isActive = json['IsActive']; + createdOn = json['CreatedOn']; + } + + Map toJson() { + final Map data = new Map(); + data['SetupId'] = this.setupId; + data['ProjectId'] = this.projectId; + data['PatientType'] = this.patientType; + data['PatientId'] = this.patientId; + data['EncounterType'] = this.encounterType; + data['EncounterNo'] = this.encounterNo; + data['OD_OS'] = this.oDOS; + data['Brand'] = this.brand; + data['BaseCurve'] = this.baseCurve; + data['Power'] = this.power; + data['Diameter'] = this.diameter; + data['OZ'] = this.oZ; + data['CT'] = this.cT; + data['Blend'] = this.blend; + data['Remarks'] = this.remarks; + data['Status'] = this.status; + data['IsActive'] = this.isActive; + data['CreatedOn'] = this.createdOn; + return data; + } +} + +class ListHISGetGlassPrescription { + dynamic projectID; + String? setupID; + dynamic patientId; + dynamic encounterType; + dynamic encounterNo; + String? visionType; + double? rightEyeSpherical; + dynamic rightEyeCylinder; + dynamic rightEyeAxis; + dynamic rightEyePrism; + dynamic rightEyeVA; + String? rightEyeRemarks; + dynamic leftEyeSpherical; + dynamic leftEyeCylinder; + dynamic leftEyeAxis; + dynamic leftEyePrism; + dynamic leftEyeVA; + String? leftEyeRemarks; + dynamic pD; + dynamic bVD; + dynamic status; + bool? isActive; + String? createdOn; + + ListHISGetGlassPrescription( + {this.projectID, + this.setupID, + this.patientId, + this.encounterType, + this.encounterNo, + this.visionType, + this.rightEyeSpherical, + this.rightEyeCylinder, + this.rightEyeAxis, + this.rightEyePrism, + this.rightEyeVA, + this.rightEyeRemarks, + this.leftEyeSpherical, + this.leftEyeCylinder, + this.leftEyeAxis, + this.leftEyePrism, + this.leftEyeVA, + this.leftEyeRemarks, + this.pD, + this.bVD, + this.status, + this.isActive, + this.createdOn}); + + ListHISGetGlassPrescription.fromJson(Map json) { + projectID = json['ProjectID']; + setupID = json['SetupID']; + patientId = json['PatientId']; + encounterType = json['EncounterType']; + encounterNo = json['EncounterNo']; + visionType = json['VisionType']; + rightEyeSpherical = json['RightEyeSpherical']; + rightEyeCylinder = json['RightEyeCylinder']; + rightEyeAxis = json['RightEyeAxis']; + rightEyePrism = json['RightEyePrism']; + rightEyeVA = json['RightEyeVA']; + rightEyeRemarks = json['RightEyeRemarks']; + leftEyeSpherical = json['LeftEyeSpherical']; + leftEyeCylinder = json['LeftEyeCylinder']; + leftEyeAxis = json['LeftEyeAxis']; + leftEyePrism = json['LeftEyePrism']; + leftEyeVA = json['LeftEyeVA']; + leftEyeRemarks = json['LeftEyeRemarks']; + pD = json['PD']; + bVD = json['BVD']; + status = json['Status']; + isActive = json['IsActive']; + createdOn = json['CreatedOn']; + } + + Map toJson() { + final Map data = new Map(); + data['ProjectID'] = this.projectID; + data['SetupID'] = this.setupID; + data['PatientId'] = this.patientId; + data['EncounterType'] = this.encounterType; + data['EncounterNo'] = this.encounterNo; + data['VisionType'] = this.visionType; + data['RightEyeSpherical'] = this.rightEyeSpherical; + data['RightEyeCylinder'] = this.rightEyeCylinder; + data['RightEyeAxis'] = this.rightEyeAxis; + data['RightEyePrism'] = this.rightEyePrism; + data['RightEyeVA'] = this.rightEyeVA; + data['RightEyeRemarks'] = this.rightEyeRemarks; + data['LeftEyeSpherical'] = this.leftEyeSpherical; + data['LeftEyeCylinder'] = this.leftEyeCylinder; + data['LeftEyeAxis'] = this.leftEyeAxis; + data['LeftEyePrism'] = this.leftEyePrism; + data['LeftEyeVA'] = this.leftEyeVA; + data['LeftEyeRemarks'] = this.leftEyeRemarks; + data['PD'] = this.pD; + data['BVD'] = this.bVD; + data['Status'] = this.status; + data['IsActive'] = this.isActive; + data['CreatedOn'] = this.createdOn; + return data; + } +} + class PatientAppointmentList { String? filterName = ""; List? patientDoctorAppointmentList = []; diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 87451c2..87ec10d 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -6,7 +6,9 @@ import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/cache_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/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart' show HospitalsModel; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; @@ -14,7 +16,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class MyAppointmentsRepo { - Future>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments}); + Future>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false}); Future>> getPatientShareAppointment( {required int projectID, required int clinicID, required String appointmentNo, required bool isLiveCareAppointment}); @@ -49,6 +51,13 @@ abstract class MyAppointmentsRepo { Future>> getTamaraInstallmentsDetails(); Future>> getActiveAppointmentsCount(); + + Future>>> getAskDoctorRequestTypes(); + + Future>> isDoctorAvailable({required int projectID, required int clinicID, required int doctorID}); + + Future>> sendAskDocCallRequest( + {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, required String requestType, required String remarks, required String userMobileNumber}); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -58,13 +67,14 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { MyAppointmentsRepoImp({required this.loggerService, required this.apiClient}); @override - Future>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments}) async { + Future>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false}) async { Map mapDevice = { "IsActiveAppointment": isActiveAppointment, "IsComingFromCOC": false, "isForUpcomming": false, "IsForMedicalReport": false, "IsForArrived": isArrivedAppointments, + "IsIrisPrescription": isForEyeMeasurement }; try { @@ -618,4 +628,122 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> isDoctorAvailable({required int projectID, required int clinicID, required int doctorID}) async { + Map mapDevice = {"isDentalAllowedBackend": false, "DoctorID": doctorID, "ProjectID": projectID, "ClinicID": clinicID}; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + IS_DOCTOR_AVAILABLE_BY_CALENDAR_SCHEDULE, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final isDoctorAvailable = response['IsDoctorAvailable']; + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: isDoctorAvailable, + ); + } 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>>> getAskDoctorRequestTypes() async { + Map mapDevice = {}; + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post(GET_CALL_REQUEST_TYPE, onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['ListReqTypes']; + + final askDoctorRequestTypesList = list.map((item) => AskDocRequestType.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: askDoctorRequestTypesList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, body: mapDevice); + 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> sendAskDocCallRequest( + {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, required String requestType, required String remarks, required String userMobileNumber}) async { + Map body = {}; + + body['ProjectID'] = patientAppointmentHistoryResponseModel.projectID; + body['SetupID'] = patientAppointmentHistoryResponseModel.setupID; + body['DoctorID'] = patientAppointmentHistoryResponseModel.doctorID; + body['PatientMobileNumber'] = userMobileNumber; + body['IsMessageSent'] = false; + body['RequestDate'] = DateUtil.convertDateToString(DateTime.now()); + body['RequestTime'] = DateUtil.convertDateToString(DateTime.now()); + body['Remarks'] = remarks; + body['Status'] = 2; // 4 for testing only.."cancelled status insert" else should be changed to 1 in live version + body['CreatedBy'] = 102; + body['CreatedOn'] = DateUtil.convertDateToString(DateTime.now()); + body['EditedBy'] = 102; + body['EditedOn'] = DateUtil.convertDateToString(DateTime.now()); + body['isDentalAllowedBackend'] = false; + body['AppointmentNo'] = patientAppointmentHistoryResponseModel.appointmentNo; + body['ClinicID'] = patientAppointmentHistoryResponseModel.clinicID; + body['QuestionType'] = num.parse(requestType); + body['RequestType'] = num.parse(requestType); + body['RequestTypeID'] = num.parse(requestType); + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post(INSERT_CALL_INFO, 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()); + } + }, body: body); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index e8b2650..fa1ad22 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/appointemnet_filters.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_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/models/resp_models/patient_appointment_share_response_model.dart'; @@ -13,6 +14,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { int selectedTabIndex = 0; int previouslySelectedTab = -1; + int eyeMeasurementsTabSelectedIndex = 0; + MyAppointmentsRepo myAppointmentsRepo; ErrorHandlerService errorHandlerService; AppState appState; @@ -26,6 +29,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { bool isAppointmentDataToBeLoaded = true; + bool isEyeMeasurementsAppointmentsLoading = false; + List availableFilters = []; List? selectedFilter = []; bool isDateFilterSelected = false; @@ -42,6 +47,10 @@ class MyAppointmentsViewModel extends ChangeNotifier { List patientMyDoctorsList = []; + List patientEyeMeasurementsAppointmentsHistoryList = []; + + List askDoctorRequestTypeList = []; + PatientAppointmentShareResponseModel? patientAppointmentShareResponseModel; GetTamaraInstallmentsDetailsResponseModel? getTamaraInstallmentsDetailsResponseModel; @@ -57,18 +66,25 @@ class MyAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + void onEyeMeasurementsTabChanged(int index) { + eyeMeasurementsTabSelectedIndex = index; + notifyListeners(); + } + initAppointmentsViewModel() { if (isAppointmentDataToBeLoaded) { patientAppointmentsHistoryList.clear(); patientUpcomingAppointmentsHistoryList.clear(); patientArrivedAppointmentsHistoryList.clear(); patientTimelineAppointmentsList.clear(); + patientEyeMeasurementsAppointmentsHistoryList.clear(); isMyAppointmentsLoading = true; isTimeLineAppointmentsLoading = true; patientMyDoctorsList.clear(); } isTamaraDetailsLoading = true; isAppointmentPatientShareLoading = true; + isEyeMeasurementsAppointmentsLoading = true; notifyListeners(); } @@ -105,6 +121,11 @@ class MyAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + setIsEyeMeasurementsAppointmentsLoading(bool val) { + isEyeMeasurementsAppointmentsLoading = val; + notifyListeners(); + } + setAppointmentReminder(bool value, PatientAppointmentHistoryResponseModel item) { int index = patientAppointmentsHistoryList.indexOf(item); if (index != -1) { @@ -113,6 +134,29 @@ class MyAppointmentsViewModel extends ChangeNotifier { } } + Future getPatientEyeMeasurementAppointments({Function(dynamic)? onSuccess, Function(String)? onError}) async { + patientEyeMeasurementsAppointmentsHistoryList.clear(); + notifyListeners(); + + final result = await myAppointmentsRepo.getPatientAppointments(isActiveAppointment: false, isArrivedAppointments: true, isForEyeMeasurement: true); + + 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) { + patientEyeMeasurementsAppointmentsHistoryList = apiResponse.data!; + isEyeMeasurementsAppointmentsLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + Future getPatientAppointments(bool isActiveAppointment, bool isArrivedAppointments, {Function(dynamic)? onSuccess, Function(String)? onError}) async { if (!isAppointmentDataToBeLoaded) return; @@ -568,4 +612,71 @@ class MyAppointmentsViewModel extends ChangeNotifier { }, ); } + + Future isDoctorAvailable({required int projectID, required int doctorId, required int clinicId, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.isDoctorAvailable(projectID: projectID, doctorID: doctorId, clinicID: clinicId); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data); + } + } + }, + ); + } + + Future getAskDoctorRequestTypes({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.getAskDoctorRequestTypes(); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + } else if (apiResponse.messageStatus == 1) { + askDoctorRequestTypeList = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data); + } + } + }, + ); + } + + Future sendAskDocCallRequest({ + required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, + required String requestType, + required String remarks, + required String userMobileNumber, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + final result = await myAppointmentsRepo.sendAskDocCallRequest( + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, requestType: requestType, remarks: remarks, userMobileNumber: userMobileNumber); + + result.fold( + // (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) async { + await errorHandlerService.handleError(failure: failure); + if (onError != null) onError(failure.message); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data); + } + } + }, + ); + } } diff --git a/lib/features/payfort/payfort_repo.dart b/lib/features/payfort/payfort_repo.dart index 74a323a..c00b01c 100644 --- a/lib/features/payfort/payfort_repo.dart +++ b/lib/features/payfort/payfort_repo.dart @@ -4,6 +4,7 @@ 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/my_appointments/models/resp_models/get_tamara_installments_details_response_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/models/payfort_check_payment_status_response_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/payfort_project_details_resp_model.dart'; @@ -25,6 +26,8 @@ abstract class PayfortRepo { Future>> updateTamaraRequestStatus( {required String responseMessage, required String status, required String clientRequestID, required String tamaraOrderID}); + + Future>> getTamaraInstallmentsDetails(); } class PayfortRepoImp implements PayfortRepo { @@ -250,4 +253,42 @@ class PayfortRepoImp implements PayfortRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> getTamaraInstallmentsDetails() async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.get( + ApiConsts.GET_TAMARA_INSTALLMENTS_URL, + isExternal: true, + isAllowAny: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response; + + final tamaraInstallmentsList = GetTamaraInstallmentsDetailsResponseModel.fromJson(list.first); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: tamaraInstallmentsList, + ); + } 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/payfort/payfort_view_model.dart b/lib/features/payfort/payfort_view_model.dart index 89effcd..db8209a 100644 --- a/lib/features/payfort/payfort_view_model.dart +++ b/lib/features/payfort/payfort_view_model.dart @@ -1,6 +1,9 @@ +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'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_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/models/payfort_check_payment_status_response_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/payfort_project_details_resp_model.dart'; @@ -21,6 +24,9 @@ class PayfortViewModel extends ChangeNotifier { late AmazonPayfort _payfort; final NetworkInfo _info = NetworkInfo(); + GetTamaraInstallmentsDetailsResponseModel? getTamaraInstallmentsDetailsResponseModel; + bool isTamaraDetailsLoading = false; + PayfortViewModel({required this.payfortRepo, required this.errorHandlerService}); setIsApplePayConfigurationLoading(bool value) { @@ -36,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( @@ -56,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( @@ -102,7 +110,7 @@ class PayfortViewModel extends ChangeNotifier { onError!(failure.message); }, (apiResponse) { - print(apiResponse.data); + log(apiResponse.data); if (onSuccess != null) { onSuccess(apiResponse); } @@ -112,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); } @@ -134,7 +148,7 @@ class PayfortViewModel extends ChangeNotifier { String? applePayShaType, String? applePayShaRequestPhrase, }) async { - var sdkTokenResponse; + SdkTokenResponse? sdkTokenResponse; try { String? deviceId = await _payfort.getDeviceId(); @@ -168,7 +182,7 @@ class PayfortViewModel extends ChangeNotifier { }, ); } catch (e) { - print("Error here: ${e.toString()}"); + log("Error here: ${e.toString()}"); } return sdkTokenResponse; } @@ -234,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( @@ -242,11 +257,28 @@ class PayfortViewModel extends ChangeNotifier { onError!(failure.message); }, (apiResponse) { - print(apiResponse.data); + log(apiResponse.data); + if (onSuccess != null) { + onSuccess(apiResponse); + } + }, + ); + } + + Future getTamaraInstallmentsDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await payfortRepo.getTamaraInstallmentsDetails(); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + getTamaraInstallmentsDetailsResponseModel = apiResponse.data!; + isTamaraDetailsLoading = false; + notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); } }, ); } + } diff --git a/lib/features/radiology/radiology_repo.dart b/lib/features/radiology/radiology_repo.dart index 0a44428..b81fd50 100644 --- a/lib/features/radiology/radiology_repo.dart +++ b/lib/features/radiology/radiology_repo.dart @@ -9,11 +9,12 @@ import 'package:hmg_patient_app_new/features/radiology/models/resp_models/patien import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class RadiologyRepo { - Future>>> getPatientRadiologyOrders({required String patientId}); + Future>>> getPatientRadiologyOrders(); Future>> getRadiologyImage({required PatientRadiologyResponseModel patientRadiologyResponseModel}); - Future>> getRadiologyReportPDF({required PatientRadiologyResponseModel patientRadiologyResponseModel, required AuthenticatedUser authenticatedUser}); + Future>> getRadiologyReportPDF( + {required PatientRadiologyResponseModel patientRadiologyResponseModel, required AuthenticatedUser authenticatedUser}); } class RadiologyRepoImp implements RadiologyRepo { @@ -23,7 +24,7 @@ class RadiologyRepoImp implements RadiologyRepo { RadiologyRepoImp({required this.loggerService, required this.apiClient}); @override - Future>>> getPatientRadiologyOrders({required String patientId}) async { + Future>>> getPatientRadiologyOrders() async { Map mapDevice = {}; try { @@ -40,10 +41,16 @@ class RadiologyRepoImp implements RadiologyRepo { try { if (response['FinalRadiologyList'] != null && response['FinalRadiologyList'].length != 0) { final list = response['FinalRadiologyList']; - radOrders = list.map((item) => PatientRadiologyResponseModel.fromJson(item as Map)).toList().cast(); + radOrders = list + .map((item) => PatientRadiologyResponseModel.fromJson(item as Map)) + .toList() + .cast(); } else { final list = response['FinalRadiologyListAPI']; - radOrders = list.map((item) => PatientRadiologyResponseModel.fromJson(item as Map)).toList().cast(); + radOrders = list + .map((item) => PatientRadiologyResponseModel.fromJson(item as Map)) + .toList() + .cast(); } apiResponse = GenericApiModel>( @@ -107,7 +114,8 @@ class RadiologyRepoImp implements RadiologyRepo { } @override - Future>> getRadiologyReportPDF({required PatientRadiologyResponseModel patientRadiologyResponseModel, required AuthenticatedUser authenticatedUser}) async { + Future>> getRadiologyReportPDF( + {required PatientRadiologyResponseModel patientRadiologyResponseModel, required AuthenticatedUser authenticatedUser}) async { Map mapDevice = { "InvoiceNo": Utils.isVidaPlusProject(patientRadiologyResponseModel.projectID!) ? 0 : patientRadiologyResponseModel.invoiceNo, "InvoiceNo_VP": Utils.isVidaPlusProject(patientRadiologyResponseModel.projectID!) ? patientRadiologyResponseModel.invoiceNo : 0, @@ -121,7 +129,8 @@ class RadiologyRepoImp implements RadiologyRepo { 'ClinicName': patientRadiologyResponseModel.clinicDescription, 'DateofBirth': authenticatedUser.dateofBirth, 'DoctorName': patientRadiologyResponseModel.doctorName, - 'OrderDate': '${patientRadiologyResponseModel.orderDate!.year}-${patientRadiologyResponseModel.orderDate!.month}-${patientRadiologyResponseModel.orderDate!.day}', + 'OrderDate': + '${patientRadiologyResponseModel.orderDate!.year}-${patientRadiologyResponseModel.orderDate!.month}-${patientRadiologyResponseModel.orderDate!.day}', 'PatientIditificationNum': authenticatedUser.patientIdentificationNo, 'PatientMobileNumber': authenticatedUser.mobileNumber, 'PatientName': "${authenticatedUser.firstName!} ${authenticatedUser.lastName!}", diff --git a/lib/features/radiology/radiology_view_model.dart b/lib/features/radiology/radiology_view_model.dart index d37d1ea..d39d84f 100644 --- a/lib/features/radiology/radiology_view_model.dart +++ b/lib/features/radiology/radiology_view_model.dart @@ -24,7 +24,7 @@ class RadiologyViewModel extends ChangeNotifier { RadiologyViewModel({required this.radiologyRepo, required this.errorHandlerService}); - initRadiologyProvider() { + initRadiologyViewModel() { patientRadiologyOrders.clear(); filteredRadiologyOrders.clear(); isRadiologyOrdersLoading = true; @@ -35,7 +35,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), @@ -57,7 +57,8 @@ class RadiologyViewModel extends ChangeNotifier { ); } - Future getRadiologyImage({required PatientRadiologyResponseModel patientRadiologyResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { + Future getRadiologyImage( + {required PatientRadiologyResponseModel patientRadiologyResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await radiologyRepo.getRadiologyImage(patientRadiologyResponseModel: patientRadiologyResponseModel); result.fold( @@ -77,8 +78,12 @@ class RadiologyViewModel extends ChangeNotifier { } Future getRadiologyPDF( - {required PatientRadiologyResponseModel patientRadiologyResponseModel, required AuthenticatedUser authenticatedUser, Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await radiologyRepo.getRadiologyReportPDF(patientRadiologyResponseModel: patientRadiologyResponseModel, authenticatedUser: authenticatedUser); + {required PatientRadiologyResponseModel patientRadiologyResponseModel, + required AuthenticatedUser authenticatedUser, + Function(dynamic)? onSuccess, + Function(String)? onError}) async { + final result = + await radiologyRepo.getRadiologyReportPDF(patientRadiologyResponseModel: patientRadiologyResponseModel, authenticatedUser: authenticatedUser); result.fold( (failure) async => await errorHandlerService.handleError( diff --git a/lib/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart b/lib/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart new file mode 100644 index 0000000..c014bee --- /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 0000000..26abde3 --- /dev/null +++ b/lib/features/todo_section/models/resp_models/ancillary_order_procedures_detail_response_model.dart @@ -0,0 +1,221 @@ +// Dart model classes for "AncillaryOrderProcList" +// Generated for user: faizatflutter +// Uses DateUtil.convertStringToDate for parsing .NET-style dates (same approach as your PatientRadiologyResponseModel) + +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; + +class AncillaryOrderProcListModel { + List? ancillaryOrderProcList; + + AncillaryOrderProcListModel({this.ancillaryOrderProcList}); + + factory AncillaryOrderProcListModel.fromJson(Map json) => AncillaryOrderProcListModel( + ancillaryOrderProcList: json['AncillaryOrderProcList'] != null + ? List.from( + (json['AncillaryOrderProcList'] as List).map( + (x) => AncillaryOrderProcedureItem.fromJson(x as Map), + ), + ) + : null, + ); +} + +class AncillaryOrderProcedureItem { + List? ancillaryOrderProcDetailsList; + DateTime? appointmentDate; + int? appointmentNo; + int? clinicID; + String? clinicName; + int? companyID; + String? companyName; + int? doctorID; + String? doctorName; + dynamic errCode; + int? groupID; + String? insurancePolicyNo; + String? message; + String? patientCardID; + int? patientID; + String? patientName; + int? patientType; + int? policyID; + String? policyName; + int? projectID; + String? setupID; + int? statusCode; + int? subCategoryID; + String? subPolicyNo; + + AncillaryOrderProcedureItem({ + this.ancillaryOrderProcDetailsList, + this.appointmentDate, + this.appointmentNo, + this.clinicID, + this.clinicName, + this.companyID, + this.companyName, + this.doctorID, + this.doctorName, + this.errCode, + this.groupID, + this.insurancePolicyNo, + this.message, + this.patientCardID, + this.patientID, + this.patientName, + this.patientType, + this.policyID, + this.policyName, + this.projectID, + this.setupID, + this.statusCode, + this.subCategoryID, + this.subPolicyNo, + }); + + factory AncillaryOrderProcedureItem.fromJson(Map json) => AncillaryOrderProcedureItem( + ancillaryOrderProcDetailsList: json['AncillaryOrderProcDetailsList'] != null + ? List.from( + (json['AncillaryOrderProcDetailsList'] as List).map( + (x) => AncillaryOrderProcDetail.fromJson(x as Map), + ), + ) + : null, + appointmentDate: DateUtil.convertStringToDate(json['AppointmentDate']), + appointmentNo: json['AppointmentNo'] as int?, + clinicID: json['ClinicID'] as int?, + clinicName: json['ClinicName'] as String?, + companyID: json['CompanyID'] as int?, + companyName: json['CompanyName'] as String?, + doctorID: json['DoctorID'] as int?, + doctorName: json['DoctorName'] as String?, + errCode: json['ErrCode'], + groupID: json['GroupID'] as int?, + insurancePolicyNo: json['InsurancePolicyNo'] as String?, + message: json['Message'] as String?, + patientCardID: json['PatientCardID'] as String?, + patientID: json['PatientID'] as int?, + patientName: json['PatientName'] as String?, + patientType: json['PatientType'] as int?, + policyID: json['PolicyID'] as int?, + policyName: json['PolicyName'] as String?, + projectID: json['ProjectID'] as int?, + setupID: json['SetupID'] as String?, + statusCode: json['StatusCode'] as int?, + subCategoryID: json['SubCategoryID'] as int?, + subPolicyNo: json['SubPolicyNo'] as String?, + ); +} + +class AncillaryOrderProcDetail { + int? approvalLineItemNo; + int? approvalNo; + String? approvalStatus; + int? approvalStatusID; + num? companyShare; + num? companyShareWithTax; + num? companyTaxAmount; + num? discountAmount; + int? discountCategory; + String? discountType; + num? discountTypeValue; + bool? isApprovalCreated; + bool? isApprovalRequired; + dynamic isCheckInAllow; + bool? isCovered; + bool? isLab; + DateTime? orderDate; + int? orderLineItemNo; + int? orderNo; + int? partnerID; + num? partnerShare; + String? partnerShareType; + num? patientShare; + num? patientShareWithTax; + num? patientTaxAmount; + num? procPrice; + int? procedureCategoryID; + String? procedureCategoryName; + String? procedureID; + String? procedureName; + num? taxAmount; + num? taxPct; + + AncillaryOrderProcDetail({ + this.approvalLineItemNo, + this.approvalNo, + this.approvalStatus, + this.approvalStatusID, + this.companyShare, + this.companyShareWithTax, + this.companyTaxAmount, + this.discountAmount, + this.discountCategory, + this.discountType, + this.discountTypeValue, + this.isApprovalCreated, + this.isApprovalRequired, + this.isCheckInAllow, + this.isCovered, + this.isLab, + this.orderDate, + this.orderLineItemNo, + this.orderNo, + this.partnerID, + this.partnerShare, + this.partnerShareType, + this.patientShare, + this.patientShareWithTax, + this.patientTaxAmount, + this.procPrice, + this.procedureCategoryID, + this.procedureCategoryName, + this.procedureID, + this.procedureName, + this.taxAmount, + this.taxPct, + }); + + factory AncillaryOrderProcDetail.fromJson(Map json) => AncillaryOrderProcDetail( + approvalLineItemNo: json['ApprovalLineItemNo'] as int?, + approvalNo: json['ApprovalNo'] as int?, + approvalStatus: json['ApprovalStatus'] as String?, + approvalStatusID: json['ApprovalStatusID'] as int?, + companyShare: _toNum(json['CompanyShare']), + companyShareWithTax: _toNum(json['CompanyShareWithTax']), + companyTaxAmount: _toNum(json['CompanyTaxAmount']), + discountAmount: _toNum(json['DiscountAmount']), + discountCategory: json['DiscountCategory'] as int?, + discountType: json['DiscountType'] as String?, + discountTypeValue: _toNum(json['DiscountTypeValue']), + isApprovalCreated: json['IsApprovalCreated'] as bool?, + isApprovalRequired: json['IsApprovalRequired'] as bool?, + isCheckInAllow: json['IsCheckInAllow'], + isCovered: json['IsCovered'] as bool?, + isLab: json['IsLab'] as bool?, + orderDate: DateUtil.convertStringToDate(json['OrderDate']), + orderLineItemNo: json['OrderLineItemNo'] as int?, + orderNo: json['OrderNo'] as int?, + partnerID: json['PartnerID'] as int?, + partnerShare: _toNum(json['PartnerShare']), + partnerShareType: json['PartnerShareType'] as String?, + patientShare: _toNum(json['PatientShare']), + patientShareWithTax: _toNum(json['PatientShareWithTax']), + patientTaxAmount: _toNum(json['PatientTaxAmount']), + procPrice: _toNum(json['ProcPrice']), + procedureCategoryID: json['ProcedureCategoryID'] as int?, + procedureCategoryName: json['ProcedureCategoryName'] as String?, + procedureID: json['ProcedureID'] as String?, + procedureName: json['ProcedureName'] as String?, + taxAmount: _toNum(json['TaxAmount']), + taxPct: _toNum(json['TaxPct']), + ); +} + +// Helper to safely parse numeric fields that may be int/double/string/null +num? _toNum(dynamic v) { + if (v == null) return null; + if (v is num) return v; + if (v is String) return num.tryParse(v); + return null; +} diff --git a/lib/features/todo_section/todo_section_repo.dart b/lib/features/todo_section/todo_section_repo.dart new file mode 100644 index 0000000..008b22c --- /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 0000000..c0fb96b --- /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 259ce3b..20c4ece 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,6 +10,7 @@ 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/features/authentication/authentication_view_model.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/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'; @@ -17,6 +18,7 @@ import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_liveca 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'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/features/location/location_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/appointment_via_region_viewmodel.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; @@ -24,6 +26,7 @@ import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; +import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -129,6 +132,14 @@ void main() async { ), 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 f5b5b7e..79084c9 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -20,6 +20,7 @@ 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/prescriptions/prescription_detail_page.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; @@ -37,9 +38,9 @@ import '../../core/dependencies.dart'; import '../medical_file/widgets/medical_file_card.dart'; class AppointmentDetailsPage extends StatefulWidget { - AppointmentDetailsPage({super.key, required this.patientAppointmentHistoryResponseModel}); + const AppointmentDetailsPage({super.key, required this.patientAppointmentHistoryResponseModel}); - PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; + final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; @override State createState() => _AppointmentDetailsPageState(); @@ -81,7 +82,35 @@ class _AppointmentDetailsPageState extends State { children: [ AppointmentDoctorCard( patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, - onAskDoctorTap: () {}, + onAskDoctorTap: () async { + LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation); + await myAppointmentsViewModel.isDoctorAvailable( + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + doctorId: widget.patientAppointmentHistoryResponseModel.doctorID, + clinicId: widget.patientAppointmentHistoryResponseModel.clinicID, + onSuccess: (value) async { + if (value) { + print("Doctor is available"); + await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.askDoctor.tr(context: context), + child: AskDoctorRequestTypeSelect( + askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList, + myAppointmentsViewModel: myAppointmentsViewModel, + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + print("Doctor is not available"); + } + }); + }, onCancelTap: () async { myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); LoaderBottomSheet.showLoader(loadingText: "Cancelling Appointment, Please Wait...".needTranslation); @@ -115,7 +144,7 @@ class _AppointmentDetailsPageState extends State { Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, - borderRadius: 20.h, + borderRadius: 20.r, hasShadow: false, ), child: Padding( @@ -138,7 +167,7 @@ class _AppointmentDetailsPageState extends State { ? Row( children: [ Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 40.h, height: 40.h), - SizedBox(width: 12.h), + SizedBox(width: 12.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -155,7 +184,8 @@ class _AppointmentDetailsPageState extends State { children: [ ClipRRect( clipBehavior: Clip.hardEdge, - borderRadius: BorderRadius.circular(24), + borderRadius: BorderRadius.circular(24.r), + // Todo: what is this???? Api Key??? 😲 child: Image.network( "https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=350x165&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng", fit: BoxFit.contain, @@ -173,19 +203,19 @@ class _AppointmentDetailsPageState extends State { double.parse(widget.patientAppointmentHistoryResponseModel.longitude!), widget.patientAppointmentHistoryResponseModel.projectName); }, - backgroundColor: AppColors.textColor.withOpacity(0.8), - borderColor: - AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction) - .withOpacity(0.01), + backgroundColor: AppColors.textColor.withValues(alpha: 0.8), + borderColor: AppointmentType.getNextActionButtonColor( + widget.patientAppointmentHistoryResponseModel.nextAction) + .withValues(alpha: 0.01), textColor: AppColors.whiteColor, fontSize: 14.f, fontWeight: FontWeight.w500, borderRadius: 12.r, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + padding: EdgeInsets.symmetric(horizontal: 10.w), height: 40.h, icon: AppAssets.directions_icon, iconColor: AppColors.whiteColor, - iconSize: 13.h, + iconSize: 14.h, ).paddingAll(12.h), ), ), @@ -199,7 +229,7 @@ class _AppointmentDetailsPageState extends State { Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, - borderRadius: 20.h, + borderRadius: 20.r, hasShadow: false, ), child: Row( @@ -211,12 +241,14 @@ class _AppointmentDetailsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), - "Notify me before the appointment".needTranslation.toText11(color: AppColors.textColorLight, weight: FontWeight.w500), + "Notify me before the appointment" + .needTranslation + .toText11(color: AppColors.textColorLight, weight: FontWeight.w500), ], ), const Spacer(), Switch( - activeColor: AppColors.successColor, + // activeThumbColor: AppColors.successColor, activeTrackColor: AppColors.successColor.withValues(alpha: .15), value: widget.patientAppointmentHistoryResponseModel.hasReminder!, onChanged: (newValue) { @@ -237,8 +269,12 @@ class _AppointmentDetailsPageState extends State { "Lab & Radiology".needTranslation.toText18(isBold: true), SizedBox(height: 16.h), GridView( + padding: EdgeInsets.zero, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, crossAxisSpacing: 13.h, mainAxisSpacing: 13.h, childAspectRatio: 7 / 6), + crossAxisCount: isTablet || isFoldable ? 3 : 2, + crossAxisSpacing: 13.w, + mainAxisSpacing: 13.w, + ), physics: NeverScrollableScrollPhysics(), shrinkWrap: true, children: [ @@ -247,19 +283,36 @@ class _AppointmentDetailsPageState extends State { textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.lab_result_icon, - iconSize: 40, + iconSize: 40.w, isLargeText: true, ), MedicalFileCard( - label: LocaleKeys.radiology.tr(context: context), + label: "Radiology Results".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.radiology_icon, - iconSize: 40, + 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) { @@ -268,153 +321,178 @@ class _AppointmentDetailsPageState extends State { : Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: Colors.white, - borderRadius: 20.h, + borderRadius: 20.r, ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - children: [ - ListView.separated( - itemCount: prescriptionVM.prescriptionDetailsList.length, - shrinkWrap: true, - padding: const EdgeInsets.only(left: 0, right: 8), - 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: [ - SizedBox( - width: 150.h, - child: prescriptionVM.prescriptionDetailsList[index].itemDescription! - .toText12(isBold: true, maxLine: 1)), - SizedBox( - width: 150.h, - child: - "Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}" - .needTranslation - .toText10( - weight: FontWeight.w500, color: AppColors.greyTextColor, letterSpacing: -0.4), - ), - ], - ), - SizedBox(width: 68.h), - Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, - iconColor: AppColors.blackColor, - width: 18.h, - height: 13.h, - fit: BoxFit.contain, - ), + 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), - Row( - 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: "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, - fontWeight: FontWeight.w500, - borderRadius: 12.h, - height: 40.h, - icon: AppAssets.requests, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ), + }, + 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, ), - ], - ), - ], - ), + ), + ], + ), + ], ), ); }), ], ), ], - ).paddingSymmetrical(24.h, 24.h), + ).paddingAll(24.w), ), ), ), Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, - borderRadius: 24.h, + borderRadius: 24.r, hasShadow: true, ), child: SizedBox( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (widget.patientAppointmentHistoryResponseModel.nextAction == 15 || widget.patientAppointmentHistoryResponseModel.nextAction == 20) + if (widget.patientAppointmentHistoryResponseModel.nextAction == 15 || + widget.patientAppointmentHistoryResponseModel.nextAction == 20) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -423,7 +501,9 @@ class _AppointmentDetailsPageState extends State { children: [ "Amount before tax".needTranslation.toText18(isBold: true), Utils.getPaymentAmountWithSymbol( - widget.patientAppointmentHistoryResponseModel.patientShare!.toString().toText16(isBold: true), AppColors.blackColor, 13, + widget.patientAppointmentHistoryResponseModel.patientShare!.toString().toText16(isBold: true), + AppColors.blackColor, + 13, isSaudiCurrency: true), ], ), @@ -432,8 +512,9 @@ class _AppointmentDetailsPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( - child: - LocaleKeys.upcomingPaymentNow.tr(context: context).toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor)), + child: LocaleKeys.upcomingPaymentNow + .tr(context: context) + .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor)), "VAT 15%(${widget.patientAppointmentHistoryResponseModel.patientTaxAmount})" .needTranslation .toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -2), @@ -470,10 +551,10 @@ class _AppointmentDetailsPageState extends State { backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, - fontSize: 16, + fontSize: 16.f, fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), height: 50.h, icon: AppAssets.add_icon, iconColor: AppColors.whiteColor, @@ -486,13 +567,14 @@ class _AppointmentDetailsPageState extends State { handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction); }, backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), - borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + borderColor: + AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), textColor: widget.patientAppointmentHistoryResponseModel.nextAction == 15 ? AppColors.textColor : AppColors.whiteColor, - fontSize: 16, + fontSize: 16.f, fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 50.h, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 56.h, icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), iconColor: AppColors.whiteColor, iconSize: 18.h, diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 467a480..faf1c2a 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'; @@ -59,19 +57,20 @@ class _AppointmentPaymentPageState extends State { void initState() { scheduleMicrotask(() { payfortViewModel.initPayfortViewModel(); - myAppointmentsViewModel.getTamaraInstallmentsDetails().then((val) { - if (myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && - myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) { - setState(() { - isShowTamara = true; - }); - } - }); payfortViewModel.setIsApplePayConfigurationLoading(false); myAppointmentsViewModel.getPatientShareAppointment( widget.patientAppointmentHistoryResponseModel.projectID, widget.patientAppointmentHistoryResponseModel.clinicID, - widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false, onError: (err) { + widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false, onSuccess: (val) { + myAppointmentsViewModel.getTamaraInstallmentsDetails().then((val) { + if (myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && + myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) { + setState(() { + isShowTamara = true; + }); + } + }); + }, onError: (err) { Navigator.of(context).pop(); Navigator.of(context).pop(); }); @@ -109,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), ], @@ -153,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), @@ -181,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, @@ -243,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: () { @@ -256,7 +266,7 @@ class _AppointmentPaymentPageState extends State { backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.secondaryLightRedBorderColor, textColor: AppColors.whiteColor, - fontSize: 10, + fontSize: 10.f, fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(15, 0, 15, 0), @@ -273,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), @@ -282,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), @@ -293,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), @@ -372,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, @@ -417,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, @@ -535,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 +569,13 @@ class _AppointmentPaymentPageState extends State { startApplePay() async { showCommonBottomSheet(context, - child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); + child: Utils.getLoadingWidget(), + callBackFunc: (str) {}, + title: "", + height: ResponsiveExtension.screenHeight * 0.3, + isCloseButtonVisible: false, + isDismissible: false, + isFullScreen: false); transID = Utils.getAppointmentTransID( widget.patientAppointmentHistoryResponseModel.projectID, widget.patientAppointmentHistoryResponseModel.clinicID, @@ -556,7 +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; @@ -625,7 +656,6 @@ class _AppointmentPaymentPageState extends State { ); }, onSucceeded: (successResult) async { - Navigator.of(context).pop(); log("successResult: ${successResult.responseMessage.toString()}"); selectedPaymentMethod = successResult.paymentOption ?? "VISA"; checkPaymentStatus(); diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index 7481bf2..934aca4 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -93,7 +93,7 @@ class _MyAppointmentsPageState extends State { children: [ Visibility(visible: myAppointmentsVM.availableFilters.isNotEmpty, child: getAppointmentFilters(myAppointmentsVM)), ListView.separated( - padding: EdgeInsets.only(top: 24.h), + padding: EdgeInsets.only(top: 16.h), shrinkWrap: true, physics: NeverScrollableScrollPhysics(), itemCount: myAppointmentsVM.isMyAppointmentsLoading @@ -169,6 +169,7 @@ class _MyAppointmentsPageState extends State { Widget getAppointmentFilters(MyAppointmentsViewModel myAppointmentsVM) { return SizedBox( + height: 60.h, child: Row( children: [ Expanded( @@ -177,6 +178,7 @@ class _MyAppointmentsPageState extends State { width: 8.h, ), scrollDirection: Axis.horizontal, + shrinkWrap: true, itemCount: myAppointmentsVM.availableFilters.length, itemBuilder: (_, index) => AppointmentFilters( selectedFilter: myAppointmentsVM.selectedFilter, @@ -205,6 +207,6 @@ class _MyAppointmentsPageState extends State { )), ), ], - )).paddingOnly(top: 24.h, left: 24.h, right: 24.h); + )).paddingOnly(top: 16.h, left: 24.h, right: 24.h); } } diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 1451bcb..e451088 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -16,346 +16,363 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/appointment_details_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/widgets/appointment_calendar.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/eye_measurement_details_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/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/routes/custom_page_route.dart'; -import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; -import 'package:smooth_corner/smooth_corner.dart'; -class AppointmentCard extends StatefulWidget { - AppointmentCard( - {super.key, - required this.patientAppointmentHistoryResponseModel, - required this.myAppointmentsViewModel, - required this.bookAppointmentsViewModel, - this.isLoading = false, - this.isFromHomePage = false, - this.isFromMedicalReport = false, - this.medicalFileViewModel}); +class AppointmentCard extends StatelessWidget { + final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; + final MyAppointmentsViewModel myAppointmentsViewModel; + final bool isLoading; + final bool isFromHomePage; + final bool isFromMedicalReport; + final bool isForEyeMeasurements; + final MedicalFileViewModel? medicalFileViewModel; + final BookAppointmentsViewModel bookAppointmentsViewModel; - PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; - MyAppointmentsViewModel myAppointmentsViewModel; - bool isLoading; - bool isFromHomePage; - bool isFromMedicalReport; - MedicalFileViewModel? medicalFileViewModel; - BookAppointmentsViewModel bookAppointmentsViewModel; + const AppointmentCard({ + super.key, + required this.patientAppointmentHistoryResponseModel, + required this.myAppointmentsViewModel, + required this.bookAppointmentsViewModel, + this.isLoading = false, + this.isFromHomePage = false, + this.isFromMedicalReport = false, + this.isForEyeMeasurements = false, + this.medicalFileViewModel, + }); - @override - State createState() => _AppointmentCardState(); -} - -class _AppointmentCardState extends State { @override Widget build(BuildContext context) { - AppState appState = getIt.get(); + final appState = getIt.get(); return InkWell( - onTap: () { - Navigator.of(context) - .push( - CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - ), - ) - .then((val) { - widget.myAppointmentsViewModel.initAppointmentsViewModel(); - widget.myAppointmentsViewModel.getPatientAppointments(true, false); - }); - }, + onTap: () => _goToDetails(context), child: Padding( padding: EdgeInsets.all(14.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Wrap( - alignment: WrapAlignment.start, - direction: Axis.horizontal, - spacing: 6.h, - runSpacing: 6.h, - children: [ - AppCustomChipWidget( - icon: widget.isLoading - ? AppAssets.walkin_appointment_icon - : (!widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppAssets.walkin_appointment_icon : AppAssets.small_livecare_icon), - iconColor: widget.isLoading - ? AppColors.textColor - : !widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! - ? AppColors.textColor - : AppColors.whiteColor, - labelText: widget.isLoading - ? "Walk In" - : widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! - ? LocaleKeys.livecare.tr(context: context) - : "Walk In".needTranslation, - backgroundColor: - widget.isLoading ? AppColors.greyColor : (!widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor), - textColor: widget.isLoading ? AppColors.textColor : (!widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : AppColors.whiteColor), - ).toShimmer2(isShow: widget.isLoading), - AppCustomChipWidget( - labelText: widget.isLoading - ? "OutPatient" - : appState.isArabic() - ? widget.patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! - : widget.patientAppointmentHistoryResponseModel.isInOutPatientDescription!, - backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), - textColor: AppColors.primaryRedColor, - ).toShimmer2(isShow: widget.isLoading), - AppCustomChipWidget( - labelText: widget.isLoading ? "Booked" : AppointmentType.getAppointmentStatusType(widget.patientAppointmentHistoryResponseModel.patientStatusType!), - backgroundColor: AppColors.successColor.withOpacity(0.1), - textColor: AppColors.successColor, - ).toShimmer2(isShow: widget.isLoading), - ], - ).toShimmer2(isShow: widget.isLoading), - ), - // TODO: Implement the logic to enable/disable the switch based on reminder status - // AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) - // ? SizedBox().toShimmer2(isShow: widget.isLoading) - // : Switch( - // activeColor: AppColors.successColor, - // activeTrackColor: AppColors.successColor.withValues(alpha: .15), - // thumbIcon: WidgetStateProperty.resolveWith( - // (Set states) { - // if (states.contains(WidgetState.selected)) { - // return const Icon(Icons.check); // Icon when switch is ON - // } - // return const Icon(Icons.close); // Icon when switch is OFF - // }, - // ), - // value: widget.isLoading ? false : widget.patientAppointmentHistoryResponseModel.hasReminder!, - // onChanged: (newValue) { - // setState(() { - // widget.myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); - // }); - // }, - // ).toShimmer2(isShow: widget.isLoading), - ], - ), + _buildHeader(context, appState), SizedBox(height: 16.h), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.network( - widget.isLoading ? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png" : widget.patientAppointmentHistoryResponseModel.doctorImageURL!, - width: 63.h, - height: 63.h, - fit: BoxFit.cover, - ).circle(100).toShimmer2(isShow: widget.isLoading), - SizedBox(width: 16.h), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - (widget.isLoading ? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png" : widget.patientAppointmentHistoryResponseModel.doctorNameObj!) - .toText16(isBold: true, maxlines: 1) - .toShimmer2(isShow: widget.isLoading), - SizedBox(height: 8.h), - Wrap( - direction: Axis.horizontal, - spacing: 3.h, - runSpacing: 4.h, - children: [ - widget.isFromHomePage - ? SizedBox.shrink() - : AppCustomChipWidget(labelText: widget.isLoading ? "Cardiology" : widget.patientAppointmentHistoryResponseModel.clinicName!).toShimmer2(isShow: widget.isLoading), - widget.isFromHomePage - ? SizedBox.shrink() - : AppCustomChipWidget(labelText: widget.isLoading ? "Olaya" : widget.patientAppointmentHistoryResponseModel.projectName!).toShimmer2(isShow: widget.isLoading), - AppCustomChipWidget( - icon: AppAssets.appointment_calendar_icon, - labelText: - widget.isLoading ? "Cardiology" : DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), false)) - .toShimmer2(isShow: widget.isLoading), - widget.isFromMedicalReport - ? SizedBox.shrink() - : AppCustomChipWidget( - icon: AppAssets.appointment_time_icon, - labelText: widget.isLoading - ? "Cardiology" - : DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), false)) - .toShimmer2(isShow: widget.isLoading), - ], - ), - ], - ), - ), - ], - ), + _buildDoctorRow(context), SizedBox(height: 16.h), - widget.isFromMedicalReport - ? CustomButton( - text: "Select appointment".needTranslation, - onPressed: () { - widget.medicalFileViewModel!.setSelectedMedicalReportAppointment(widget.patientAppointmentHistoryResponseModel); - Navigator.pop(context, false); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - icon: AppAssets.checkmark_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ) - : Row( - children: [ - Expanded( - flex: 6, - child: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) - ? getArrivedAppointmentButton().toShimmer2(isShow: widget.isLoading) - : CustomButton( - text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), - onPressed: () { - Navigator.of(context) - .push(CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - )) - .then((val) { - widget.myAppointmentsViewModel.initAppointmentsViewModel(); - widget.myAppointmentsViewModel.getPatientAppointments(true, false); - }); - }, - backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15), - borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), - textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), - iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), - iconSize: 15.h, - ).toShimmer2(isShow: widget.isLoading), - ), - SizedBox(width: 8.h), - Expanded( - flex: 1, - child: Container( - height: 40.h, - width: 40.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.textColor, - borderRadius: 10.h, - ), - child: Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, - iconColor: AppColors.whiteColor, - width: 40.h, - height: 40.h, - fit: BoxFit.cover, - ), - ), - ).toShimmer2(isShow: widget.isLoading).onPress(() { - Navigator.of(context) - .push( - CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - ), - ) - .then((val) { - widget.myAppointmentsViewModel.initAppointmentsViewModel(); - widget.myAppointmentsViewModel.getPatientAppointments(true, false); - }); - }), - ), - ], - ), + _buildActionArea(context, appState), ], ), ), ); } - Widget getArrivedAppointmentButton() { - return DateTime.now().difference(DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15 - ? CustomButton( - text: LocaleKeys.askDoctor.tr(context: context), - onPressed: () {}, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - icon: AppAssets.ask_doctor_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ) - : CustomButton( - text: "Rebook with same doctor".needTranslation, - onPressed: () { - openDoctorScheduleCalendar(); - }, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - icon: AppAssets.rebook_appointment_icon, - iconColor: AppColors.blackColor, - iconSize: 16.h, - ); + Widget _buildHeader(BuildContext context, AppState appState) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: _buildChips(context, appState)), + ], + ); + } + + Widget _buildChips(BuildContext context, AppState appState) { + final isLiveCare = !isLoading && patientAppointmentHistoryResponseModel.isLiveCareAppointment!; + + return Wrap( + alignment: WrapAlignment.start, + direction: Axis.horizontal, + spacing: 6.w, + runSpacing: 6.h, + children: [ + AppCustomChipWidget( + icon: isLoading ? AppAssets.walkin_appointment_icon : (isLiveCare ? AppAssets.small_livecare_icon : AppAssets.walkin_appointment_icon), + iconColor: isLoading ? AppColors.textColor : (isLiveCare ? AppColors.whiteColor : AppColors.textColor), + labelText: isLoading ? 'Walk In'.needTranslation : (isLiveCare ? LocaleKeys.livecare.tr(context: context) : 'Walk In'.needTranslation), + backgroundColor: isLoading ? AppColors.greyColor : (isLiveCare ? AppColors.successColor : AppColors.greyColor), + textColor: isLoading ? AppColors.textColor : (isLiveCare ? AppColors.whiteColor : AppColors.textColor), + ).toShimmer2(isShow: isLoading), + AppCustomChipWidget( + labelText: isLoading + ? 'OutPatient'.needTranslation + : (appState.isArabic() + ? patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! + : patientAppointmentHistoryResponseModel.isInOutPatientDescription!), + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1), + textColor: AppColors.primaryRedColor, + ).toShimmer2(isShow: isLoading), + AppCustomChipWidget( + labelText: isLoading + ? 'Booked'.needTranslation + : AppointmentType.getAppointmentStatusType(patientAppointmentHistoryResponseModel.patientStatusType!), + backgroundColor: AppColors.successColor.withValues(alpha: 0.1), + textColor: AppColors.successColor, + ).toShimmer2(isShow: isLoading), + ], + ).toShimmer2(isShow: isLoading); } - openDoctorScheduleCalendar() async { - DoctorsListResponseModel doctor = DoctorsListResponseModel( - clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, - projectID: widget.patientAppointmentHistoryResponseModel.projectID, - doctorID: widget.patientAppointmentHistoryResponseModel.doctorID, - doctorImageURL: widget.patientAppointmentHistoryResponseModel.doctorImageURL, - doctorTitle: widget.patientAppointmentHistoryResponseModel.doctorTitle, - name: widget.patientAppointmentHistoryResponseModel.doctorNameObj, - nationalityFlagURL: "https://hmgwebservices.com/Images/flag/SYR.png", - speciality: [], - clinicName: widget.patientAppointmentHistoryResponseModel.clinicName, - projectName: widget.patientAppointmentHistoryResponseModel.projectName, + Widget _buildDoctorRow(BuildContext context) { + 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), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (isLoading + ? 'https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png' + : patientAppointmentHistoryResponseModel.doctorNameObj!) + .toText16(isBold: true, maxlines: 1) + .toShimmer2(isShow: isLoading), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + 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), + ).toShimmer2(isShow: isLoading), + if (!isFromMedicalReport) + AppCustomChipWidget( + icon: AppAssets.appointment_time_icon, + labelText: isLoading + ? 'Cardiology' + : DateUtil.formatDateToTimeLang( + DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false), + ).toShimmer2(isShow: isLoading), + ], + ), + ], + ), + ), + ], ); - widget.bookAppointmentsViewModel.setSelectedDoctor(doctor); - LoaderBottomSheet.showLoader(); - await widget.bookAppointmentsViewModel.getDoctorFreeSlots( - isBookingForLiveCare: false, - onSuccess: (dynamic respData) async { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - title: "Pick a Date".needTranslation, - context, - child: AppointmentCalendar(), - isFullScreen: false, - isCloseButtonVisible: true, - callBackFunc: () {}, - ); + } + + Widget _buildActionArea(BuildContext context, AppState appState) { + if (isFromMedicalReport) { + if (isForEyeMeasurements) { + return SizedBox.shrink(); + } else { + return CustomButton( + text: 'Select appointment'.needTranslation, + onPressed: () { + medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel); + Navigator.pop(context, false); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: isTablet || isFoldable ? 46.h : 40.h, + icon: AppAssets.checkmark_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ); + } + } + + return Row( + children: [ + Expanded( + flex: 6, + child: (AppointmentType.isArrived(patientAppointmentHistoryResponseModel) + ? _getArrivedButton(context) + : CustomButton( + text: AppointmentType.getNextActionText(patientAppointmentHistoryResponseModel.nextAction), + onPressed: () => _goToDetails(context), + backgroundColor: + AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15), + borderColor: + AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), + textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: isTablet || isFoldable ? 46.h : 40.h, + icon: AppointmentType.getNextActionIcon(patientAppointmentHistoryResponseModel.nextAction), + iconColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), + iconSize: 15.h, + )) + .toShimmer2(isShow: isLoading), + ), + SizedBox(width: 8.h), + Expanded( + flex: 1, + child: Container( + height: (isFoldable || isTablet) ? 50.h : 40.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.textColor, borderRadius: 10.h), + child: Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.whiteColor, + width: 24.w, + height: 24.h, + fit: BoxFit.contain, + ), + ), + ).toShimmer2(isShow: isLoading).onPress(() { + _goToDetails(context); + }), + ), + ], + ); + } + + Widget _getArrivedButton(BuildContext context) { + final isRecent = DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15; + + if (isRecent) { + return CustomButton( + text: LocaleKeys.askDoctor.tr(context: context), + onPressed: () async { + LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation); + await myAppointmentsViewModel.isDoctorAvailable( + projectID: patientAppointmentHistoryResponseModel.projectID, + doctorId: patientAppointmentHistoryResponseModel.doctorID, + clinicId: patientAppointmentHistoryResponseModel.clinicID, + onSuccess: (value) async { + if (value) { + print("Doctor is available"); + await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.askDoctor.tr(context: context), + child: AskDoctorRequestTypeSelect( + askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList, + myAppointmentsViewModel: myAppointmentsViewModel, + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + print("Doctor is not available"); + } + }); }, - onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: isTablet || isFoldable ? 46.h : 40.h, + icon: AppAssets.ask_doctor_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ); + } + + return CustomButton( + text: 'Rebook with same doctor'.needTranslation, + onPressed: () => openDoctorScheduleCalendar(context), + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: isTablet || isFoldable ? 46.h : 40.h, + icon: AppAssets.rebook_appointment_icon, + iconColor: AppColors.blackColor, + iconSize: 16.h, + ); + } + + void _goToDetails(BuildContext context) { + if (isForEyeMeasurements) { + Navigator.of(context).push( + CustomPageRoute( + page: EyeMeasurementDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), + ), + ); + } else { + Navigator.of(context) + .push( + CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), + ), + ) + .then((_) { + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + }); + } } - void performAppointmentNextAction(nextAction) {} + void openDoctorScheduleCalendar(BuildContext context) async { + final doctor = DoctorsListResponseModel( + clinicID: patientAppointmentHistoryResponseModel.clinicID, + projectID: patientAppointmentHistoryResponseModel.projectID, + doctorID: patientAppointmentHistoryResponseModel.doctorID, + doctorImageURL: patientAppointmentHistoryResponseModel.doctorImageURL, + doctorTitle: patientAppointmentHistoryResponseModel.doctorTitle, + name: patientAppointmentHistoryResponseModel.doctorNameObj, + nationalityFlagURL: 'https://hmgwebservices.com/Images/flag/SYR.png', + speciality: [], + clinicName: patientAppointmentHistoryResponseModel.clinicName, + projectName: patientAppointmentHistoryResponseModel.projectName, + ); + + bookAppointmentsViewModel.setSelectedDoctor(doctor); + LoaderBottomSheet.showLoader(); + + await bookAppointmentsViewModel.getDoctorFreeSlots( + isBookingForLiveCare: false, + onSuccess: (respData) async { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: AppointmentCalendar(), + callBackFunc: () {}, + title: 'Pick a Date'.needTranslation, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + ); + } } diff --git a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart index 1863875..74ab6b7 100644 --- a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart +++ b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart @@ -18,6 +18,7 @@ import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:barcode_scan2/barcode_scan2.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/nfc/nfc_reader_sheet.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; @@ -139,14 +140,13 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { } void sendCheckInRequest(String scannedCode, BuildContext context) async { - showCommonBottomSheet(context, - child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); + LoaderBottomSheet.showLoader(loadingText: "Processing Check-In...".needTranslation); await myAppointmentsViewModel.sendCheckInNfcRequest( patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, scannedCode: scannedCode, checkInType: 2, onSuccess: (apiResponse) { - Navigator.of(context).pop(); + LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(context, title: "Success".needTranslation, child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), callBackFunc: () { Navigator.of(context).pop(); Navigator.pushAndRemoveUntil( @@ -161,7 +161,7 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { }, isFullScreen: false); }, onError: (error) { - Navigator.of(context).pop(); + LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(context, title: "Error".needTranslation, child: Utils.getErrorWidget(loadingText: error), callBackFunc: () { Navigator.of(context).pop(); }, isFullScreen: false); diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index efc5a6b..8d0f4e7 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -13,12 +13,18 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; class AppointmentDoctorCard extends StatelessWidget { - AppointmentDoctorCard({super.key, required this.patientAppointmentHistoryResponseModel, required this.onRescheduleTap, required this.onCancelTap, required this.onAskDoctorTap}); + const AppointmentDoctorCard( + {super.key, + required this.patientAppointmentHistoryResponseModel, + required this.onRescheduleTap, + required this.onCancelTap, + required this.onAskDoctorTap, this.renderWidgetForERDisplay = false}); - PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; - late Function onRescheduleTap; - late Function onCancelTap; - late Function onAskDoctorTap; + final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; + final VoidCallback onRescheduleTap; + final VoidCallback onCancelTap; + final VoidCallback onAskDoctorTap; + final bool renderWidgetForERDisplay; @override Widget build(BuildContext context) { @@ -38,11 +44,11 @@ class AppointmentDoctorCard extends StatelessWidget { children: [ Image.network( patientAppointmentHistoryResponseModel.doctorImageURL!, - width: 63.h, + width: 63.w, height: 63.h, fit: BoxFit.cover, - ).circle(100), - SizedBox(width: 16.h), + ).circle(100.r), + SizedBox(width: 16.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -50,23 +56,41 @@ class AppointmentDoctorCard extends StatelessWidget { patientAppointmentHistoryResponseModel.doctorNameObj!.toText16(isBold: true), Wrap( direction: Axis.horizontal, - spacing: 3.h, - runSpacing: 4.h, + spacing: 6.w, + runSpacing: 6.h, children: [ - AppCustomChipWidget(labelText: patientAppointmentHistoryResponseModel.clinicName!), - AppCustomChipWidget(labelText: patientAppointmentHistoryResponseModel.projectName!), AppCustomChipWidget( - icon: AppAssets.doctor_calendar_icon, - labelText: - "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}, ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}"), + labelText: patientAppointmentHistoryResponseModel.clinicName!, + labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), + ), + AppCustomChipWidget( + labelText: patientAppointmentHistoryResponseModel.projectName!, + labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w), + ), + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: + "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}, ${DateUtil.formatDateToTimeLang( + DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), + false, + )}", + ), AppCustomChipWidget( - icon: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppAssets.walkin_appointment_icon : AppAssets.small_livecare_icon, + icon: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! + ? AppAssets.walkin_appointment_icon + : AppAssets.small_livecare_icon, iconColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : AppColors.whiteColor, - labelText: patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? LocaleKeys.livecare.tr(context: context) : "Walk In".needTranslation, - backgroundColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor, + labelText: patientAppointmentHistoryResponseModel.isLiveCareAppointment! + ? LocaleKeys.livecare.tr(context: context) + : "Walk In".needTranslation, + backgroundColor: + !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor, textColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : AppColors.whiteColor, ), - AppCustomChipWidget(icon: AppAssets.rating_icon, iconColor: AppColors.ratingColorYellow, labelText: "Rating: ${patientAppointmentHistoryResponseModel.decimalDoctorRate}"), + AppCustomChipWidget( + icon: AppAssets.rating_icon, + iconColor: AppColors.ratingColorYellow, + labelText: "Rating: ${patientAppointmentHistoryResponseModel.decimalDoctorRate}"), ], ), ], @@ -75,8 +99,12 @@ class AppointmentDoctorCard extends StatelessWidget { ], ), SizedBox(height: 16.h), - getAppointmentActionButtons( - AppointmentType.isArrived(patientAppointmentHistoryResponseModel), + + Visibility( + visible: renderWidgetForERDisplay == false, + child: getAppointmentActionButtons( + AppointmentType.isArrived(patientAppointmentHistoryResponseModel), + ), ), ], ), @@ -89,14 +117,16 @@ class AppointmentDoctorCard extends StatelessWidget { return DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15 ? CustomButton( text: LocaleKeys.askDoctor.tr(), - onPressed: () {}, + onPressed: () { + onAskDoctorTap(); + }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, - fontSize: 14, + fontSize: 14.f, fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + borderRadius: 12.r, + padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), height: 40.h, iconSize: 16.h, icon: AppAssets.ask_doctor_icon, @@ -110,10 +140,10 @@ class AppointmentDoctorCard extends StatelessWidget { backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, - fontSize: 14, + fontSize: 14.f, fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + borderRadius: 12.r, + padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), height: 40.h, icon: AppAssets.rebook_appointment_icon, iconColor: AppColors.blackColor, @@ -129,9 +159,9 @@ class AppointmentDoctorCard extends StatelessWidget { backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, - fontSize: 14, + fontSize: 14.f, fontWeight: FontWeight.w500, - borderRadius: 12.h, + borderRadius: 12.r, height: 40.h, icon: AppAssets.cancel, iconColor: AppColors.whiteColor, @@ -148,9 +178,9 @@ class AppointmentDoctorCard extends StatelessWidget { backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, - fontSize: 14, + fontSize: 14.f, fontWeight: FontWeight.w500, - borderRadius: 12.h, + borderRadius: 12.r, height: 40.h, icon: AppAssets.appointment_calendar_icon, iconColor: AppColors.primaryRedColor, @@ -167,9 +197,9 @@ class AppointmentDoctorCard extends StatelessWidget { backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, - fontSize: 14, + fontSize: 14.f, fontWeight: FontWeight.w500, - borderRadius: 12.h, + borderRadius: 12.r, height: 40.h, icon: AppAssets.cancel, iconColor: AppColors.whiteColor, diff --git a/lib/presentation/appointments/widgets/ask_doctor_request_type_select.dart b/lib/presentation/appointments/widgets/ask_doctor_request_type_select.dart new file mode 100644 index 0000000..9f06a4d --- /dev/null +++ b/lib/presentation/appointments/widgets/ask_doctor_request_type_select.dart @@ -0,0 +1,138 @@ +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/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/ask_doctor_request_type_response_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/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'; + +class AskDoctorRequestTypeSelect extends StatelessWidget { + AskDoctorRequestTypeSelect({super.key, required this.askDoctorRequestTypeList, required this.myAppointmentsViewModel, required this.patientAppointmentHistoryResponseModel}); + + final MyAppointmentsViewModel myAppointmentsViewModel; + final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; + List askDoctorRequestTypeList = []; + int selectedParameterCodeValue = 2; + int selectedParameterCode = 0; + + final ValueNotifier requestTypeSelectNotifier = ValueNotifier(0); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24), + child: ListView.builder( + itemCount: askDoctorRequestTypeList.length, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.only(top: 8, bottom: 8), + shrinkWrap: true, + itemBuilder: (context, index) { + return ValueListenableBuilder( + valueListenable: requestTypeSelectNotifier, + builder: (context, duration, child) { + return Theme( + data: Theme.of(context).copyWith( + listTileTheme: ListTileThemeData(horizontalTitleGap: 4), + ), + child: RadioListTile( + title: (askDoctorRequestTypeList[index].description ?? '').toText14(weight: FontWeight.w500), + value: 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: selectedParameterCode, + onChanged: (int? newValue) { + selectedParameterCode = newValue!; + selectedParameterCodeValue = askDoctorRequestTypeList[index].parameterCode!; + requestTypeSelectNotifier.value = selectedParameterCode; + debugPrint(selectedParameterCodeValue.toString()); + }, + ), + ); + }, + ); + }, + ), + ), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.cancel.tr(), + onPressed: () { + Navigator.pop(context); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + icon: AppAssets.cancel, + iconColor: AppColors.whiteColor, + borderRadius: 12.r, + iconSize: 14.h, + fontSize: 14.f, + height: 40.h, + ), + ), + SizedBox(width: 8.h), + Expanded( + child: CustomButton( + text: LocaleKeys.confirm.tr(), + onPressed: () async { + Navigator.pop(context); + LoaderBottomSheet.showLoader(loadingText: "Sending Request..."); + await myAppointmentsViewModel.sendAskDocCallRequest( + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + requestType: selectedParameterCodeValue.toString(), + remarks: "", + userMobileNumber: myAppointmentsViewModel.appState.getAuthenticatedUser()!.mobileNumber!, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getSuccessWidget(loadingText: "Request has been sent successfully, you will be contacted soon.".needTranslation), + callBackFunc: () { + Navigator.of(context).pop(); + }, + title: "", + isCloseButtonVisible: true, + isDismissible: false, + isFullScreen: false, + ); + }, + onError: (errMessage) { + LoaderBottomSheet.hideLoader(); + }); + }, + backgroundColor: AppColors.bgGreenColor, + borderColor: AppColors.bgGreenColor, + textColor: Colors.white, + icon: AppAssets.confirm, + iconSize: 14.h, + borderRadius: 12.r, + fontSize: 14.f, + height: 40.h, + ), + ), + ], + ) + ], + ); + } +} diff --git a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart index 31dc74e..660f95e 100644 --- a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart +++ b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart @@ -72,6 +72,7 @@ class HospitalListItem extends StatelessWidget { ); Widget get distanceInfo => Row( + spacing: 4.w, children: [ Visibility( visible: (hospitalData?.distanceInKMs != "0"), @@ -91,9 +92,9 @@ class HospitalListItem extends StatelessWidget { labelText: "Distance not available".needTranslation, textColor: AppColors.blackColor, ), - SizedBox( - width: 8.h, - ) + // SizedBox( + // width: 8.h, + // ) ], )), Visibility( @@ -101,7 +102,7 @@ class HospitalListItem extends StatelessWidget { child: AppCustomChipWidget( labelText: "Location turned off".needTranslation, deleteIcon: AppAssets.location_unavailable, - deleteIconSize: Size(9, 12), + deleteIconSize: Size(9.w, 12.h), textColor: AppColors.blackColor, )), ], diff --git a/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart index 8b35921..754db53 100644 --- a/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart +++ b/lib/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart @@ -61,7 +61,7 @@ class RegionListItem extends StatelessWidget { Widget placesCountItem(String svgPath, String count, String title) { return AppCustomChipWidget( - iconSize: 14, + iconSize: 14.h, icon: svgPath, iconHasColor: false, richText: RichText( diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart index cfa4437..93b26c7 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -1,6 +1,5 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.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'; @@ -12,7 +11,6 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; -import 'package:hmg_patient_app_new/presentation/home/landing_page.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/app_bar_widget.dart'; @@ -21,7 +19,7 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:provider/provider.dart'; class SavedLogin extends StatefulWidget { - const SavedLogin({Key? key}) : super(key: key); + const SavedLogin({super.key}); @override _SavedLogin createState() => _SavedLogin(); @@ -65,248 +63,255 @@ class _SavedLogin extends State { body: SafeArea( child: Padding( padding: EdgeInsets.symmetric(horizontal: 24.h), - child: appState.getSelectDeviceByImeiRespModelElement != null ? Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Spacer(flex: 2), - // Welcome back text - LocaleKeys.welcomeBack.tr().toText16(color: AppColors.inputLabelTextColor), - SizedBox(height: 16.h), - appState.getSelectDeviceByImeiRespModelElement != null - ? appState.getSelectDeviceByImeiRespModelElement!.name!.toCamelCase.toText26(isBold: true, height: 26 / 36, color: AppColors.textColor) - : SizedBox(), - SizedBox(height: 24.h), - Container( - padding: EdgeInsets.all(16.h), - decoration: BoxDecoration( - color: AppColors.whiteColor, - border: Border.all(color: AppColors.whiteColor), - borderRadius: BorderRadius.circular(24.h), - boxShadow: [ - BoxShadow(color: Color(0x0D000000), blurRadius: 16, offset: Offset(0, 0), spreadRadius: 5), - ], - ), - child: Column( + child: appState.getSelectDeviceByImeiRespModelElement != null + ? Column( + crossAxisAlignment: CrossAxisAlignment.center, children: [ - // Last login info - - ("${LocaleKeys.lastLoginBy.tr()} ${loginType.displayName}").toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -1), + const Spacer(flex: 2), + // Welcome back text + LocaleKeys.welcomeBack.tr().toText16(color: AppColors.inputLabelTextColor), + SizedBox(height: 16.h), appState.getSelectDeviceByImeiRespModelElement != null - ? (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null - ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), "d MMMM, y 'at' HH:mm") - : '--') - .toText16(isBold: true, color: AppColors.textColor) + ? appState.getSelectDeviceByImeiRespModelElement!.name!.toCamelCase + .toText26(isBold: true, height: 26 / 36, color: AppColors.textColor) : SizedBox(), + SizedBox(height: 24.h), + Container( + padding: EdgeInsets.all(16.h), + decoration: BoxDecoration( + color: AppColors.whiteColor, + border: Border.all(color: AppColors.whiteColor), + borderRadius: BorderRadius.circular(24.h), + boxShadow: [ + BoxShadow(color: Color(0x0D000000), blurRadius: 16.h, offset: Offset(0, 0), spreadRadius: 5.h), + ], + ), + child: Column( + children: [ + // Last login info - appState.getSelectDeviceByImeiRespModelElement != null - ? Container( - margin: EdgeInsets.all(16.h), - child: Utils.buildSvgWithAssets( - icon: getTypeIcons(appState.getSelectDeviceByImeiRespModelElement!.logInType!), - height: 54, - width: 54, - iconColor: loginType.toInt == 4 ? null : AppColors.primaryRedColor)) - : SizedBox(), - // Face ID login button - SizedBox( - height: 45, - child: CustomButton( - text: "${LocaleKeys.loginBy.tr()} ${loginType.displayName}", - onPressed: () { - if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { - authVm.loginWithFingerPrintFace(() {}); - } else { - authVm.checkUserAuthentication(otpTypeEnum: loginType == LoginTypeEnum.sms ? OTPTypeEnum.sms : OTPTypeEnum.whatsapp); - } - }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: Colors.white, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(0, 10, 0, 10), - icon: getTypeIcons(loginType.toInt), - //loginType == LoginTypeEnum.sms ? AppAssets.sms :AppAssets.whatsapp, - iconColor: loginType != LoginTypeEnum.whatsapp ? Colors.white : null, + ("${LocaleKeys.lastLoginBy.tr()} ${loginType.displayName}") + .toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -1), + appState.getSelectDeviceByImeiRespModelElement != null + ? (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null + ? DateUtil.getFormattedDate( + DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), + "d MMMM, y 'at' HH:mm") + : '--') + .toText16(isBold: true, color: AppColors.textColor) + : SizedBox(), + + appState.getSelectDeviceByImeiRespModelElement != null + ? Container( + margin: EdgeInsets.all(16.h), + child: Utils.buildSvgWithAssets( + icon: getTypeIcons(appState.getSelectDeviceByImeiRespModelElement!.logInType!), + height: 54.h, + width: 54.w, + iconColor: loginType.toInt == 4 ? null : AppColors.primaryRedColor)) + : SizedBox(), + // Face ID login button + CustomButton( + text: "${LocaleKeys.loginBy.tr()} ${loginType.displayName}", + onPressed: () { + if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { + authVm.loginWithFingerPrintFace(() {}); + } else { + authVm.checkUserAuthentication(otpTypeEnum: loginType == LoginTypeEnum.sms ? OTPTypeEnum.sms : OTPTypeEnum.whatsapp); + } + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: Colors.white, + fontSize: 12.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + height: 40.h, + padding: EdgeInsets.symmetric(vertical: 10.h), + icon: getTypeIcons(loginType.toInt), + //loginType == LoginTypeEnum.sms ? AppAssets.sms :AppAssets.whatsapp, + iconColor: loginType != LoginTypeEnum.whatsapp ? Colors.white : null, + ), + ], ), ), - ], - ), - ), - const SizedBox(height: 24), - Padding( - padding: EdgeInsets.symmetric(horizontal: 16.0), - child: Text( - LocaleKeys.oR.tr(), - style: context.dynamicTextStyle(fontSize: 16, fontWeight: FontWeight.w500), - ), - ), - const SizedBox(height: 24), - // OTP login button - loginType.toInt != 1 - ? Column( - children: [ - loginType.toInt != 1 - ? CustomButton( - text: LocaleKeys.loginByOTP.tr(), - onPressed: () { - showModalBottomSheet( - context: context, - isScrollControlled: true, - isDismissible: false, - useSafeArea: true, - backgroundColor: Colors.transparent, - enableDrag: false, - // Prevent dragging to avoid focus conflicts - builder: (bottomSheetContext) => StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) { - return Padding( - padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom), - child: SingleChildScrollView( - child: GenericBottomSheet( - countryCode: "966", - initialPhoneNumber: "", - textController: TextEditingController(), - isFromSavedLogin: true, - isEnableCountryDropdown: true, - onCountryChange: (value) {}, - onChange: (String? value) {}, - buttons: [ - Padding( - padding: EdgeInsets.only(bottom: 10.h), - child: CustomButton( - text: LocaleKeys.sendOTPSMS.tr(), - onPressed: () { - Navigator.of(context).pop(); - loginType = LoginTypeEnum.sms; - authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms); - }, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: AppColors.whiteColor, - icon: AppAssets.sms), - ), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding(padding: EdgeInsets.symmetric(horizontal: 8.h), child: (LocaleKeys.oR.tr()).toText16(color: AppColors.textColor)), - ], - ), - Padding( - padding: const EdgeInsets.only(bottom: 10, top: 10), - child: CustomButton( - text: LocaleKeys.sendOTPWHATSAPP.tr(), - onPressed: () { - Navigator.of(context).pop(); - loginType = LoginTypeEnum.whatsapp; - authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp); - }, - backgroundColor: AppColors.transparent, - borderColor: AppColors.textColor, - textColor: AppColors.textColor, - icon: AppAssets.whatsapp, - iconColor: null, + SizedBox(height: 24.h), + Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w), + child: Text( + LocaleKeys.oR.tr(), + style: context.dynamicTextStyle(fontSize: 16.f, fontWeight: FontWeight.w500), + ), + ), + SizedBox(height: 24.h), + // OTP login button + loginType.toInt != 1 + ? Column( + children: [ + loginType.toInt != 1 + ? CustomButton( + text: LocaleKeys.loginByOTP.tr(), + onPressed: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + isDismissible: false, + useSafeArea: true, + backgroundColor: Colors.transparent, + enableDrag: false, + // Prevent dragging to avoid focus conflicts + builder: (bottomSheetContext) => + StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) { + return Padding( + padding: EdgeInsets.only(bottom: MediaQuery.of(bottomSheetContext).viewInsets.bottom), + child: SingleChildScrollView( + child: GenericBottomSheet( + countryCode: "966", + initialPhoneNumber: "", + textController: TextEditingController(), + isFromSavedLogin: true, + isEnableCountryDropdown: true, + onCountryChange: (value) {}, + onChange: (String? value) {}, + buttons: [ + Padding( + padding: EdgeInsets.only(bottom: 10.h), + child: CustomButton( + text: LocaleKeys.sendOTPSMS.tr(), + onPressed: () { + Navigator.of(context).pop(); + loginType = LoginTypeEnum.sms; + authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + icon: AppAssets.sms), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 8.h), + child: (LocaleKeys.oR.tr()).toText16(color: AppColors.textColor)), + ], + ), + Padding( + padding: EdgeInsets.only(bottom: 10.h, top: 10.h), + child: CustomButton( + text: LocaleKeys.sendOTPWHATSAPP.tr(), + onPressed: () { + Navigator.of(context).pop(); + loginType = LoginTypeEnum.whatsapp; + authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp); + }, + backgroundColor: AppColors.transparent, + borderColor: AppColors.textColor, + textColor: AppColors.textColor, + icon: AppAssets.whatsapp, + iconColor: null, + ), + ), + ], ), ), - ], - ), - ), - ); - }), - ); - }, - backgroundColor: AppColors.whiteColor, - borderColor: AppColors.borderOnlyColor, - textColor: AppColors.textColor, - borderWidth: 2, - padding: EdgeInsets.fromLTRB(0, 14, 0, 14), - icon: AppAssets.sms, - iconColor: AppColors.textColor, - ) - : Container(), + ); + }), + ); + }, + backgroundColor: AppColors.whiteColor, + borderColor: AppColors.borderOnlyColor, + textColor: AppColors.textColor, + borderWidth: 2, + padding: EdgeInsets.fromLTRB(0, 14.h, 0, 14.h), + icon: AppAssets.sms, + iconColor: AppColors.textColor, + ) + : Container(), + SizedBox( + height: 20.h, + ), + ], + ) + : CustomButton( + text: "${LocaleKeys.loginBy.tr()} ${LoginTypeEnum.whatsapp.displayName}", + icon: AppAssets.whatsapp, + iconColor: null, + onPressed: () { + if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { + authVm.loginWithFingerPrintFace(() {}); + } else { + loginType = LoginTypeEnum.whatsapp; + authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp); + } + }, + backgroundColor: AppColors.whiteColor, + borderColor: Color(0xFF2E3039), + textColor: Color(0xFF2E3039), + borderWidth: 2.w, + padding: EdgeInsets.fromLTRB(0, 14.h, 0, 14.h), + ), + const Spacer(flex: 2), + // OR divider + + SizedBox(height: 24.h), + // Guest and Switch account + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: SizedBox( + height: 56.h, + child: CustomButton( + text: LocaleKeys.guest.tr(), + onPressed: () { + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (BuildContext context) => LandingNavigation()), + ); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.fromLTRB(0, 10.h, 0, 10.h), + // icon: "assets/images/svg/apple-finder.svg", + ), + ), + ), SizedBox( - height: 20, + width: MediaQuery.of(context).size.width * 0.05, + ), + Expanded( + child: SizedBox( + height: 56.h, + child: CustomButton( + text: LocaleKeys.switchAccount.tr(), + onPressed: () async { + await authVm.clearDefaultInputValues(); + Navigator.of(context).push( + MaterialPageRoute(builder: (BuildContext context) => LoginScreen()), + ); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 15.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.fromLTRB(0, 10.h, 0, 10.h), + // icon: "assets/images/svg/apple-finder.svg", + ), + ), ), ], - ) - : CustomButton( - text: "${LocaleKeys.loginBy.tr()} ${LoginTypeEnum.whatsapp.displayName}", - icon: AppAssets.whatsapp, - iconColor: null, - onPressed: () { - if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { - authVm.loginWithFingerPrintFace(() {}); - } else { - loginType = LoginTypeEnum.whatsapp; - authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp); - } - }, - backgroundColor: AppColors.whiteColor, - borderColor: Color(0xFF2E3039), - textColor: Color(0xFF2E3039), - borderWidth: 2, - padding: EdgeInsets.fromLTRB(0, 14, 0, 14), ), - const Spacer(flex: 2), - // OR divider - - const SizedBox(height: 24), - // Guest and Switch account - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: SizedBox( - height: 56, - child: CustomButton( - text: LocaleKeys.guest.tr(), - onPressed: () { - Navigator.of(context).pushReplacement( - MaterialPageRoute(builder: (BuildContext context) => LandingNavigation()), - ); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 16, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(0, 10, 0, 10), - // icon: "assets/images/svg/apple-finder.svg", - ), - ), - ), - SizedBox( - width: MediaQuery.of(context).size.width * 0.05, - ), - Expanded( - child: SizedBox( - height: 56, - child: CustomButton( - text: LocaleKeys.switchAccount.tr(), - onPressed: () async { - await authVm.clearDefaultInputValues(); - Navigator.of(context).push( - MaterialPageRoute(builder: (BuildContext context) => LoginScreen()), - ); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Color(0xffED1C2B), - fontSize: 15, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(0, 10, 0, 10), - // icon: "assets/images/svg/apple-finder.svg", - ), - ), - ), - ], - ), - const SizedBox(height: 20), - ], - ) : SizedBox.shrink(), + SizedBox(height: 20.h), + ], + ) + : SizedBox.shrink(), ), ), ); diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index b39c51e..a66ea4d 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -13,7 +13,6 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_view_ import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_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/resp_models/doctor_list_api_response.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart' show RegionBottomSheetBody; @@ -21,14 +20,13 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immed import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/search_doctor_by_name.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart'; -import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/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' show showCommonBottomSheetWithoutHeight; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.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/transitions/fade_page.dart'; import 'package:provider/provider.dart'; import '../appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart'; @@ -69,7 +67,6 @@ class _BookAppointmentPageState extends State { backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.bookAppo.tr(context: context), - isLeading: Navigator.of(context).canPop(), child: SingleChildScrollView( child: Consumer(builder: (context, bookAppointmentsVM, child) { return Column( @@ -130,7 +127,9 @@ class _BookAppointmentPageState extends State { ], ), Transform.flip( - flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), ], ).onPress(() { bookAppointmentsViewModel.setIsClinicsListLoading(true); @@ -163,7 +162,9 @@ class _BookAppointmentPageState extends State { ], ), Transform.flip( - flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), ], ).onPress(() { bookAppointmentsViewModel.setIsDoctorSearchByNameStarted(false); @@ -194,7 +195,9 @@ class _BookAppointmentPageState extends State { ], ), Transform.flip( - flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), ], ).onPress(() { bookAppointmentsViewModel.setProjectID(null); @@ -214,115 +217,121 @@ class _BookAppointmentPageState extends State { Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, - borderRadius: 24.h, - 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.search_by_clinic_icon, width: 40.h, height: 40.h), - SizedBox(width: 12.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Immediate Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), - "Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), - ], - ), - ], - ), - Transform.flip( - flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), - ], - ).onPress(() async { - //TODO Implement API to check for existing LiveCare Requests + borderRadius: 24.h, + 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.search_by_clinic_icon, width: 40.h, height: 40.h), + SizedBox(width: 12.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Immediate Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), + "Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + ], + ), + ], + ), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), + ], + ).onPress(() async { + //TODO Implement API to check for existing LiveCare Requests - LoaderBottomSheet.showLoader(); - await immediateLiveCareViewModel.getPatientLiveCareHistory(); - LoaderBottomSheet.hideLoader(); + LoaderBottomSheet.showLoader(); + await immediateLiveCareViewModel.getPatientLiveCareHistory(); + LoaderBottomSheet.hideLoader(); - if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) { - Navigator.of(context).push( - CustomPageRoute( - page: ImmediateLiveCarePendingRequestPage(), - ), - ); - } else { - Navigator.of(context).push( - CustomPageRoute( - page: SelectImmediateLiveCareClinicPage(), - ), - ); - } - }), - SizedBox(height: 16.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), - SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.search_by_doctor_icon, width: 40.h, height: 40.h), - SizedBox(width: 12.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Scheduled Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), - "Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), - ], - ), - ], - ), - Transform.flip( - flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), - ], - ).onPress(() { - bookAppointmentsViewModel.setIsClinicsListLoading(true); - bookAppointmentsViewModel.setIsLiveCareSchedule(true); - Navigator.of(context).push( - CustomPageRoute( - page: SelectClinicPage(), - ), - ); - }), - SizedBox(height: 16.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), - SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.search_by_region_icon, width: 40.h, height: 40.h), - SizedBox(width: 12.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Pharma LiveCare".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), - "".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), - ], - ), - ], - ), - Transform.flip( - flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), - ], - ).onPress(() { - openRegionListBottomSheet(context, RegionBottomSheetType.FOR_REGION); - }), - ], - ), - ), - ), - ], + if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) { + Navigator.of(context).push( + CustomPageRoute( + page: ImmediateLiveCarePendingRequestPage(), + ), + ); + } else { + Navigator.of(context).push( + CustomPageRoute( + page: SelectImmediateLiveCareClinicPage(), + ), + ); + } + }), + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.search_by_doctor_icon, width: 40.h, height: 40.h), + SizedBox(width: 12.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Scheduled Consultation".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), + "Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + ], + ), + ], + ), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), + ], + ).onPress(() { + bookAppointmentsViewModel.setIsClinicsListLoading(true); + bookAppointmentsViewModel.setIsLiveCareSchedule(true); + Navigator.of(context).push( + CustomPageRoute( + page: SelectClinicPage(), + ), + ); + }), + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.search_by_region_icon, width: 40.h, height: 40.h), + SizedBox(width: 12.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Pharma LiveCare".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), + "".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + ], + ), + ], + ), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)), + ], + ).onPress(() { + openRegionListBottomSheet(context, RegionBottomSheetType.FOR_REGION); + }), + ], + ), + ), + ), + ], ).paddingSymmetrical(24.h, 0.h) : getLiveCareNotLoggedInUI(); default: @@ -335,8 +344,10 @@ class _BookAppointmentPageState extends State { regionalViewModel.flush(); regionalViewModel.setBottomSheetType(type); // AppointmentViaRegionViewmodel? viewmodel = null; - showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer(builder: (_, data, __) => getTitle(data)), isDismissible: false, - child: Consumer(builder: (_, data, __) { + showCommonBottomSheetWithoutHeight(context, + title: "", + titleWidget: Consumer(builder: (_, data, __) => getTitle(data)), + isDismissible: false, child: Consumer(builder: (_, data, __) { return getRegionalSelectionWidget(data); }), callBackFunc: () {}); } @@ -395,7 +406,9 @@ class _BookAppointmentPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ "Immediate service".needTranslation.toText18(color: AppColors.textColor, isBold: true), - "No need to wait, you will get medical consultation immediately via video call".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + "No need to wait, you will get medical consultation immediately via video call" + .needTranslation + .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ), @@ -427,7 +440,9 @@ class _BookAppointmentPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ "Doctor will contact".needTranslation.toText18(color: AppColors.textColor, isBold: true), - "A specialised doctor will contact you and will be able to view your medical history".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + "A specialised doctor will contact you and will be able to view your medical history" + .needTranslation + .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ), @@ -443,7 +458,9 @@ class _BookAppointmentPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ "Free medicine delivery".needTranslation.toText18(color: AppColors.textColor, isBold: true), - "Offers free medicine delivery for the LiveCare appointment".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + "Offers free medicine delivery for the LiveCare appointment" + .needTranslation + .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), ), @@ -458,9 +475,9 @@ class _BookAppointmentPageState extends State { backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, - fontSize: 16, + fontSize: 16.f, fontWeight: FontWeight.w500, - borderRadius: 12, + borderRadius: 12.r, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, icon: AppAssets.login1, diff --git a/lib/presentation/book_appointment/doctor_filter/doctors_filter.dart b/lib/presentation/book_appointment/doctor_filter/doctors_filter.dart index 2785449..6e51f8a 100644 --- a/lib/presentation/book_appointment/doctor_filter/doctors_filter.dart +++ b/lib/presentation/book_appointment/doctor_filter/doctors_filter.dart @@ -180,10 +180,10 @@ class DoctorsFilters extends StatelessWidget{ backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, textColor: Colors.white, - fontSize: 16, + fontSize: 16.f, padding: EdgeInsets.zero, fontWeight: FontWeight.w500, - borderRadius: 12, + borderRadius: 10.r, icon: AppAssets.add_icon, iconColor: AppColors.primaryRedColor, ).paddingAll(24.h), diff --git a/lib/presentation/book_appointment/search_doctor_by_name.dart b/lib/presentation/book_appointment/search_doctor_by_name.dart index 5949ef5..bdb6773 100644 --- a/lib/presentation/book_appointment/search_doctor_by_name.dart +++ b/lib/presentation/book_appointment/search_doctor_by_name.dart @@ -46,7 +46,7 @@ class _SearchDoctorByNameState extends State { body: Column( children: [ Expanded( - child: CollapsingListView( + child: CollapsingListView( title: "Choose Doctor".needTranslation, child: SingleChildScrollView( child: Padding( @@ -250,9 +250,9 @@ class _SearchDoctorByNameState extends State { backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, - fontSize: 16, + fontSize: 16.f, fontWeight: FontWeight.w500, - borderRadius: 12, + borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, icon: AppAssets.search_icon, diff --git a/lib/presentation/book_appointment/select_livecare_clinic_page.dart b/lib/presentation/book_appointment/select_livecare_clinic_page.dart index 12a99bc..719452d 100644 --- a/lib/presentation/book_appointment/select_livecare_clinic_page.dart +++ b/lib/presentation/book_appointment/select_livecare_clinic_page.dart @@ -107,9 +107,9 @@ class SelectLivecareClinicPage extends StatelessWidget { backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, - fontSize: 16, + fontSize: 16.f, fontWeight: FontWeight.w500, - borderRadius: 12, + borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, icon: AppAssets.livecare_book_icon, @@ -122,18 +122,13 @@ class SelectLivecareClinicPage extends StatelessWidget { onPressed: () { Navigator.of(context).pop(); onNegativeClicked?.call(); - // Navigator.of(context).push( - // CustomPageRoute( - // page: SelectDoctorPage(), - // ), - // ); }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, - fontSize: 16, + fontSize: 16.f, fontWeight: FontWeight.w500, - borderRadius: 12, + borderRadius: 10.r, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, ).paddingSymmetrical(24.h, 0.h), diff --git a/lib/presentation/book_appointment/widgets/appointment_calendar.dart b/lib/presentation/book_appointment/widgets/appointment_calendar.dart index ff966c2..d695ea6 100644 --- a/lib/presentation/book_appointment/widgets/appointment_calendar.dart +++ b/lib/presentation/book_appointment/widgets/appointment_calendar.dart @@ -111,7 +111,7 @@ class _AppointmentCalendarState extends State { selectionDecoration: ShapeDecoration( color: AppColors.transparent, shape: SmoothRectangleBorder( - borderRadius: BorderRadius.circular(12 ?? 0), + borderRadius: BorderRadius.circular(10.r), smoothness: 1, side: BorderSide(color: AppColors.primaryRedColor, width: 1.5), ), @@ -246,10 +246,10 @@ class _AppointmentCalendarState extends State { backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, - fontSize: 16, + fontSize: 16.f, fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + borderRadius: 10.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), height: 50.h, ), ], diff --git a/lib/presentation/book_appointment/widgets/doctor_card.dart b/lib/presentation/book_appointment/widgets/doctor_card.dart index 3f3d51c..1a91f01 100644 --- a/lib/presentation/book_appointment/widgets/doctor_card.dart +++ b/lib/presentation/book_appointment/widgets/doctor_card.dart @@ -16,11 +16,17 @@ import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; class DoctorCard extends StatelessWidget { - DoctorCard({super.key, required this.doctorsListResponseModel, required this.isLoading, required this.bookAppointmentsViewModel}); + const DoctorCard({ + super.key, + required this.doctorsListResponseModel, + required this.isLoading, + required this.bookAppointmentsViewModel, + }); - DoctorsListResponseModel doctorsListResponseModel; - bool isLoading = false; - BookAppointmentsViewModel bookAppointmentsViewModel; + final DoctorsListResponseModel doctorsListResponseModel; + final bool isLoading; + + final BookAppointmentsViewModel bookAppointmentsViewModel; @override Widget build(BuildContext context) { @@ -55,10 +61,14 @@ class DoctorCard extends StatelessWidget { children: [ SizedBox( width: MediaQuery.of(context).size.width * 0.49, - child: (isLoading ? "Dr John Smith" : "${doctorsListResponseModel.doctorTitle} ${doctorsListResponseModel.name}").toString().toText16(isBold: true, maxlines: 1), + child: (isLoading ? "Dr John Smith" : "${doctorsListResponseModel.doctorTitle} ${doctorsListResponseModel.name}") + .toString() + .toText16(isBold: true, maxlines: 1), ).toShimmer2(isShow: isLoading), Image.network( - isLoading ? "https://hmgwebservices.com/Images/flag/SYR.png" : doctorsListResponseModel.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SYR.png", + isLoading + ? "https://hmgwebservices.com/Images/flag/SYR.png" + : doctorsListResponseModel.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SYR.png", width: 20.h, height: 15.h, fit: BoxFit.fill, @@ -79,7 +89,8 @@ class DoctorCard extends StatelessWidget { ), Expanded( flex: 1, - child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).toShimmer2(isShow: isLoading), + child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown) + .toShimmer2(isShow: isLoading), ), ], ), @@ -159,10 +170,10 @@ class DoctorCard extends StatelessWidget { backgroundColor: Color(0xffFEE9EA), borderColor: Color(0xffFEE9EA), textColor: Color(0xffED1C2B), - fontSize: 14, + fontSize: 14.f, fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + borderRadius: 10.r, + padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), height: 40.h, icon: AppAssets.add_icon, iconColor: AppColors.primaryRedColor, diff --git a/lib/presentation/contact_us/contact_us.dart b/lib/presentation/contact_us/contact_us.dart new file mode 100644 index 0000000..6890fb4 --- /dev/null +++ b/lib/presentation/contact_us/contact_us.dart @@ -0,0 +1,114 @@ +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/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/contact_us/contact_us_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.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'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:provider/provider.dart'; + +class ContactUs extends StatelessWidget { + ContactUs({super.key}); + + late AppState appState; + late ContactUsViewModel contactUsViewModel; + late LocationUtils locationUtils; + + @override + Widget build(BuildContext context) { + appState = getIt.get(); + locationUtils = getIt.get(); + locationUtils.isShowConfirmDialog = true; + contactUsViewModel = Provider.of(context); + return Column( + children: [ + checkInOptionCard( + AppAssets.checkin_location_icon, + LocaleKeys.findUs.tr(), + "View your nearest HMG locations".needTranslation, + ).onPress(() { + locationUtils.getCurrentLocation(onSuccess: (value) { + contactUsViewModel.initContactUsViewModel(); + Navigator.pop(context); + Navigator.of(context).push( + CustomPageRoute( + page: FindUsPage(), + ), + ); + }); + }), + SizedBox(height: 16.h), + checkInOptionCard( + AppAssets.checkin_location_icon, + LocaleKeys.feedback.tr(), + "Provide your feedback on our services".needTranslation, + ), + SizedBox(height: 16.h), + checkInOptionCard( + AppAssets.checkin_location_icon, + LocaleKeys.liveChat.tr(), + "Live chat option with HMG".needTranslation, + ).onPress(() { + locationUtils.getCurrentLocation(onSuccess: (value) { + Navigator.pop(context); + Navigator.of(context).push( + CustomPageRoute( + page: LiveChatPage(), + ), + ); + }); + }), + ], + ); + } + + Widget checkInOptionCard(String icon, String title, String subTitle) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: icon, width: 40.h, height: 40.h, fit: BoxFit.fill), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.toText16(isBold: true, color: AppColors.textColor), + subTitle.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + ], + ), + ), + 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, + ), + ), + ], + ), + ], + ).paddingAll(16.h), + ); + } +} diff --git a/lib/presentation/contact_us/find_us_page.dart b/lib/presentation/contact_us/find_us_page.dart new file mode 100644 index 0000000..a4e4258 --- /dev/null +++ b/lib/presentation/contact_us/find_us_page.dart @@ -0,0 +1,165 @@ +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_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/features/medical_file/models/patient_sickleave_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/contact_us/widgets/find_us_item_card.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_sick_leave_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:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; +import 'package:provider/provider.dart'; + +class FindUsPage extends StatelessWidget { + FindUsPage({super.key}); + + late AppState appState; + late ContactUsViewModel contactUsViewModel; + + @override + Widget build(BuildContext context) { + contactUsViewModel = Provider.of(context); + appState = getIt.get(); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: LocaleKeys.location.tr(), + child: Consumer(builder: (context, contactUsVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + contactUsVM.isHMGLocationsListLoading + ? SizedBox.shrink() + : CustomTabBar( + activeTextColor: AppColors.primaryRedColor, + activeBackgroundColor: AppColors.primaryRedColor.withValues(alpha: .1), + tabs: [ + CustomTabBarModel(null, LocaleKeys.hmgHospitals.tr()), + CustomTabBarModel(null, LocaleKeys.pharmaciesList.tr()), + ], + onTabChange: (index) { + contactUsVM.setHMGHospitalsListSelected(index == 0); + }, + ).paddingSymmetrical(24.h, 0.h), + ListView.separated( + padding: EdgeInsets.only(top: 16.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: contactUsVM.isHMGLocationsListLoading + ? 5 + : contactUsVM.isHMGHospitalsListSelected + ? contactUsVM.hmgHospitalsLocationsList.length + : contactUsVM.hmgPharmacyLocationsList.length, + itemBuilder: (context, index) { + return contactUsVM.isHMGLocationsListLoading + ? 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) + : contactUsVM.isHMGHospitalsListSelected + // ? contactUsVM.hmgHospitalsLocationsList.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, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: FindUsItemCard( + getHMGLocationsModel: contactUsVM.hmgHospitalsLocationsList[index], + ), + ).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: FindUsItemCard( + getHMGLocationsModel: contactUsVM.hmgPharmacyLocationsList[index], + ), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ); + // : Utils.getNoDataWidget( + // context, + // noDataText: "No any locations yet.".needTranslation, + // ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + SizedBox(height: 24.h), + // FindUsItemCard(), + // FindUsItemCard(), + // FindUsItemCard(), + ], + ); + }), + ), + ); + } +} diff --git a/lib/presentation/contact_us/live_chat_page.dart b/lib/presentation/contact_us/live_chat_page.dart new file mode 100644 index 0000000..aced678 --- /dev/null +++ b/lib/presentation/contact_us/live_chat_page.dart @@ -0,0 +1,27 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.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'; + +class LiveChatPage extends StatelessWidget { + const LiveChatPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: LocaleKeys.liveChat.tr(), + child: SingleChildScrollView(), + ), + ), + Container() + ], + ), + ); + } +} diff --git a/lib/presentation/contact_us/widgets/find_us_item_card.dart b/lib/presentation/contact_us/widgets/find_us_item_card.dart new file mode 100644 index 0000000..a59959f --- /dev/null +++ b/lib/presentation/contact_us/widgets/find_us_item_card.dart @@ -0,0 +1,106 @@ +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/contact_us/models/resp_models/get_hmg_locations.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/chip/app_custom_chip_widget.dart'; +import 'package:maps_launcher/maps_launcher.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class FindUsItemCard extends StatelessWidget { + FindUsItemCard({super.key, required this.getHMGLocationsModel}); + + late AppState appState; + GetHMGLocationsModel getHMGLocationsModel; + + @override + Widget build(BuildContext context) { + appState = getIt.get(); + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: false, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.h, + children: [hospitalName, distanceInfo], + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ); + } + + Widget get hospitalName => Row( + children: [ + Image.network( + getHMGLocationsModel.projectImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", + width: 40.h, + height: 40.h, + fit: BoxFit.cover, + ).circle(100).toShimmer2(isShow: false).paddingOnly(right: 10), + Expanded( + child: Text( + getHMGLocationsModel.locationName!, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: AppColors.blackColor, + ), + ), + ) + ], + ); + + Widget get distanceInfo => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + AppCustomChipWidget( + labelText: "${getHMGLocationsModel.distanceInKilometers ?? ""} km".needTranslation, + icon: AppAssets.location_red, + iconColor: AppColors.primaryRedColor, + backgroundColor: AppColors.secondaryLightRedColor, + textColor: AppColors.errorColor, + ), + Row( + children: [ + AppCustomChipWidget( + labelText: "Get Directions".needTranslation, + icon: AppAssets.directions_icon, + iconColor: AppColors.whiteColor, + backgroundColor: AppColors.textColor.withValues(alpha: 0.8), + textColor: AppColors.whiteColor, + onChipTap: () { + MapsLauncher.launchCoordinates(double.parse(getHMGLocationsModel.latitude ?? "0.0"), double.parse(getHMGLocationsModel.longitude ?? "0.0"), getHMGLocationsModel.locationName!); + }, + ), + SizedBox(width: 4.w), + AppCustomChipWidget( + labelText: LocaleKeys.callNow.tr(), + icon: AppAssets.call_fill, + iconColor: AppColors.whiteColor, + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 1.0), + textColor: AppColors.whiteColor, + onChipTap: () { + launchUrl(Uri.parse("tel://" + "${getHMGLocationsModel.phoneNumber}")); + }, + ), + ], + ), + ], + ); +} 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 0e23d49..4d22610 100644 --- a/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart +++ b/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart @@ -12,8 +12,8 @@ class RrtRequestTypeSelect extends StatelessWidget { @override Widget build(BuildContext context) { - // return Consumer(builder: (context, emergencyServicesVM, child) { - return Column( + return Consumer(builder: (context, emergencyServicesVM, child) { + return Column( children: [ Container( padding: EdgeInsets.all(16.h), @@ -41,6 +41,6 @@ class RrtRequestTypeSelect extends StatelessWidget { SizedBox(height: 32.h), ], ); - // }); + }); } } 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 eb36f92..5e4b885 100644 --- a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart +++ b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart @@ -8,10 +8,19 @@ 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_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'; 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'; @@ -30,38 +39,79 @@ class CallAmbulancePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - bottomSheet: ExpandableBottomSheet( - bottomSheetType: - context.watch().bottomSheetType, - children: { - BottomSheetType.EXPANDED: ExpanedBottomSheet(context), - BottomSheetType.FIXED: FixedBottomSheet(context), - }, + 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: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ExpandableBottomSheet( + bottomSheetType: + context.watch().bottomSheetType, + children: { + BottomSheetType.EXPANDED: ExpanedBottomSheet(context), + BottomSheetType.FIXED: FixedBottomSheet(context), + }, + ), + ], ), body: Stack( children: [ - if (context.read().isGMSAvailable || - Platform.isIOS) + if (context.read().isGMSAvailable ) GMSMap( currentLocation: context.read().getGMSLocation(), onCameraMoved: (value) => context - .read() - .handleGMSMapCameraMoved(value)) + .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)), + .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), - ).paddingOnly(top: 51.h, left: 24.h).onPress((){ - Navigator.pop(context); - }) + icon: AppAssets.closeBottomNav, width: 32.h, height: 32.h) + .onPress(() { + context + .read() + .flushPickupInformation(); + Navigator.pop(context); + }), + ).paddingOnly(top: 51.h, left: 24.h), ], ), ); @@ -70,33 +120,34 @@ class CallAmbulancePage extends StatelessWidget { Widget FixedBottomSheet(BuildContext context) { return GestureDetector( onVerticalDragUpdate: (details){ - print("the delta is ${details.delta.dy}"); - - if(details.delta.dy<0){ - context.read().updateBottomSheetState(BottomSheetType.EXPANDED); - } + // if(details.delta.dy<0){ + // + // context.read().updateBottomSheetState(BottomSheetType.EXPANDED); + // } }, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Padding( - - padding: EdgeInsetsDirectional.only(end:24.h, bottom: 24.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), - ), - ), - ], - ), + // Row( + // mainAxisAlignment: MainAxisAlignment.end, + // children: [ + // Padding( + // + // padding: EdgeInsetsDirectional.only(end:24.h, bottom: 24.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(); + // }), + // ), + // ), + // ], + // ), // PositionedDirectional( // end: 1, // child: Padding( @@ -116,25 +167,7 @@ class CallAmbulancePage extends StatelessWidget { mainAxisSize: MainAxisSize.min, spacing: 24.h, children: [ - TextInputWidget( - labelText: "Enter Pickup Location Manually".needTranslation, - hintText: "Enter Pickup Location".needTranslation, - 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(() { - context - .read() - .updateBottomSheetState(BottomSheetType.EXPANDED); - }).paddingOnly(right: 24.h, left: 24.h), + inputFields(context), SizedBox( height: 200.h, child: DecoratedBox( @@ -152,11 +185,11 @@ class CallAmbulancePage extends StatelessWidget { Column( spacing: 4.h, children: [ - "Select Pickup Location".needTranslation.toText21( + "Select Pickup Details".needTranslation.toText21( weight: FontWeight.w600, color: AppColors.textColor, ), - " Please select the location of pickup" + " Please select the details of pickup" .needTranslation .toText12( fontWeight: FontWeight.w500, @@ -165,7 +198,7 @@ class CallAmbulancePage extends StatelessWidget { ], ), CustomButton( - text: "Select Location".needTranslation, + text: "Select Details".needTranslation, onPressed: () { context .read() @@ -202,21 +235,25 @@ class CallAmbulancePage extends StatelessWidget { ), child: Column( children: [ - SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 16.h, - children: [ - "Select Pickup Location".needTranslation.toText21( - weight: FontWeight.w600, - color: AppColors.textColor, - ), - locationsSections(context), - hospitalAndPickUpSection(context), - - ], - ).paddingOnly(top: 24.h, bottom: 32.h,left: 24.h, right: 24.h), - ), + Column( + 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); + }) + + ], + ).paddingOnly(top: 24.h, bottom: 32.h,left: 24.h, right: 24.h), bottomPriceContent(context) ], @@ -281,44 +318,74 @@ class CallAmbulancePage extends StatelessWidget { return DecoratedBox( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, - borderRadius: 24.h, + 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: "Select Hospital".needTranslation, - subTitle: "hospital".needTranslation, - leadingIcon: AppAssets.hospital, + title: "Pick".needTranslation, + subTitle: "Inside the home".needTranslation, + leadingIcon: AppAssets.pickup_bed, ), - Utils.buildSvgWithAssets(icon: AppAssets.down_cheveron, - width: 24.h, height: 24.h) - .paddingAll(16.h) + CustomSwitch( + value: context + .watch() + .pickupFromInsideTheLocation, + onChanged: (value){ + context + .read() + .updateThePickupPlaceFromLocation(value); + }, + ) ], - ).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, + title: '', + subTitle: "Have any appointment".needTranslation, + leadingIcon: AppAssets.appointment_checkin_icon, ), CustomSwitch( - value: context.watch().isGMSAvailable, - onChanged: (value){ - context.read().setIsGMSAvailable( value); + value: context + .watch() + .haveAnAppointment, + onChanged: (value) async { + // if (value) { + // openAppointmentList(context); + // } + await context.read() + .updateAppointment(value); + if (context.read().appointments?.isNotEmpty == true) { + openAppointmentList(context); + } }, ) ], @@ -354,13 +421,22 @@ class CallAmbulancePage extends StatelessWidget { this.leadingIcon(leadingIcon), Expanded( child: Column( + mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, children: [ - title.toText12( - color: AppColors.greyTextColor, - fontWeight: FontWeight.w500, + Visibility( + visible: title.isNotEmpty, + child: Column( + children: [ + title.toText12( + color: AppColors.greyTextColor, + fontWeight: FontWeight.w500, + ), + SizedBox(height: 2.h), + ], + ), ), - SizedBox(height: 2.h), subTitle.toText14( color: AppColors.textColor, weight: FontWeight.w500, @@ -418,17 +494,30 @@ class CallAmbulancePage extends StatelessWidget { ], ), ), - "\$250".toText24( - fontWeight: FontWeight.w600, - color: AppColors.textColor, - ) - ], - ), - CustomButton( - text: "Submit Request".needTranslation, - onPressed: () { - Navigator.push(context, CustomPageRoute(page: RequestingServicesPage())); - }) + 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), ), @@ -441,7 +530,105 @@ class CallAmbulancePage extends StatelessWidget { title: LocaleKeys.selectHospital.tr(), context, - child: HospitalBottomSheetBody(), + 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 + inputFields(BuildContext context) { + return context.read().callingPlace == + AmbulanceCallingPlace.FROM_HOSPITAL + ? HospitalFieldFirstThanPlaces(context) + : PlaceFirstThanHospitalField(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, @@ -449,4 +636,56 @@ class CallAmbulancePage extends StatelessWidget { 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/call_ambulance/requesting_services_page.dart b/lib/presentation/emergency_services/call_ambulance/requesting_services_page.dart index 7f79be0..83c16b6 100644 --- a/lib/presentation/emergency_services/call_ambulance/requesting_services_page.dart +++ b/lib/presentation/emergency_services/call_ambulance/requesting_services_page.dart @@ -5,27 +5,17 @@ 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/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/request_model/PatientER_RC.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/tracking_screen.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart' show CustomPageRoute; import 'package:lottie/lottie.dart'; -class RequestingServicesPage extends StatefulWidget { - @override - State createState() => _RequestingServicesPageState(); -} +class RequestingServicesPage extends StatelessWidget { -class _RequestingServicesPageState extends State { - @override - void initState() { - Timer(Duration(seconds: 3), () { - Navigator.pushReplacement( - context, CustomPageRoute(page: TrackingScreen())); - }); - super.initState(); - } + const RequestingServicesPage({super.key,}); @override Widget build(BuildContext context) { return Material( diff --git a/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart b/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart index 999d382..eddca11 100644 --- a/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart +++ b/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart @@ -7,7 +7,7 @@ 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/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/AmbulanceRequestOrdersModel.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'; @@ -18,52 +18,81 @@ import 'package:hmg_patient_app_new/widgets/order_tracking/order_tracking_state. import 'package:hmg_patient_app_new/widgets/order_tracking/request_tracking.dart'; import 'package:lottie/lottie.dart'; import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart' show launchUrl; class TrackingScreen extends StatelessWidget { - final OrderTrackingState state = OrderTrackingState.ended; + OrderTrackingState? state ; + final AmbulanceRequestOrdersModel? order; + + TrackingScreen({super.key, OrderTrackingState? state, this.order}){ + if(state == null){ + switch (order?.statusId) { + case 1: //pending + case 2: //processing + this.state = OrderTrackingState.waitingForCall; + break; + case 3: //completed + this.state = OrderTrackingState.dispactched; + break; + case 4: //cancel + case 6: + case 7: + this.state = OrderTrackingState.cancel; + break; + } + + }else { + this.state = state; + } + } + @override Widget build(BuildContext context) { return Scaffold( bottomNavigationBar: Visibility( - visible: state == OrderTrackingState.ended, + visible: state == OrderTrackingState.ended, child: SafeArea( child: CustomButton( - height: 56.h, - backgroundColor: AppColors.bgGreenColor, - borderColor: Colors.transparent, - text: "Close".needTranslation, - textColor: AppColors.whiteColor, - onPressed: () {}, - ).paddingOnly(left:16.h,right:16.h), + height: 56.h, + backgroundColor: AppColors.bgGreenColor, + borderColor: Colors.transparent, + text: "Close".needTranslation, + textColor: AppColors.whiteColor, + onPressed: () {}, + ).paddingOnly(left: 16.h, right: 16.h), ), ), body: CollapsingListView( title: "Tracking Details".needTranslation, child: SingleChildScrollView( child: Column( - children: [ - animationSection(), - Column( - spacing: 16.h, - children: [OrderStatus(context), OrderTrackingId(), ContactSection()], - ).paddingAll(16.h), - ], - ))), + children: [ + animationSection(), + Column( + spacing: 16.h, + children: [orderStatus(context), orderTrackingId(), contactSection()], + ).paddingAll(16.h), + ], + ))), ); } - animationSection(){ + + animationSection() { switch (state) { case OrderTrackingState.waitingForCall: case OrderTrackingState.dispactched: case OrderTrackingState.returning: return Lottie.asset(AppAnimations.ambulance, repeat: true, reverse: false, width: 260.h, height: 200.h); case OrderTrackingState.ended: - default: - return Lottie.asset(AppAnimations.checkmark, repeat: true, width: 260.h, height: 200.h); + return Lottie.asset(AppAnimations.checkmark, repeat: true, width: 260.h, height: 200.h); + case OrderTrackingState.failed: + case OrderTrackingState.cancel: + case null: + return Lottie.asset(AppAnimations.errorAnimation, repeat: true, width: 260.h, height: 200.h); } } - OrderStatus(BuildContext context) { + orderStatus(BuildContext context) { return Container( padding: EdgeInsets.all(16.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -90,15 +119,23 @@ class TrackingScreen extends StatelessWidget { } Widget trackingBottomSection(BuildContext context) { - switch (state) { + if(state == null ) + { + return SizedBox.shrink(); + } + switch (state!) { case OrderTrackingState.waitingForCall: return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox(height: 16,), + SizedBox( + height: 16, + ), CustomButton( text: "Cancel Request".needTranslation, - onPressed: () async {}, + onPressed: () async { + openCancelOrderBottomSheet(context); + }, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, textColor: Colors.white, @@ -118,7 +155,7 @@ class TrackingScreen extends StatelessWidget { height: 40.h, backgroundColor: AppColors.lightRedButtonColor, borderColor: Colors.transparent, - text: "Share Your Live Locatin on Whatsapp".needTranslation, + text: "Share Your Live Location on Whatsapp".needTranslation, fontSize: 12.f, textColor: AppColors.primaryRedColor, iconColor: AppColors.primaryRedColor, @@ -131,14 +168,9 @@ class TrackingScreen extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, spacing: 12.h, - children: [ - dividerSection, - doctorSection(), - mapSection(context) - ], + children: [dividerSection, doctorSection(), mapSection(context)], ); case OrderTrackingState.ended: - default: return Column( crossAxisAlignment: CrossAxisAlignment.start, spacing: 12.h, @@ -147,6 +179,10 @@ class TrackingScreen extends StatelessWidget { doctorSection(), ], ); + case OrderTrackingState.failed: + case OrderTrackingState.cancel: + return SizedBox.shrink(); + } } @@ -170,17 +206,14 @@ class TrackingScreen extends StatelessWidget { return Row( spacing: 16.h, children: [ - Utils.buildImgWithNetwork(url: "", iconColor: Colors.transparent) - .circle(52.h), + Utils.buildImgWithNetwork(url: "", iconColor: Colors.transparent).circle(52.h), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Dr. John Doe".toText16( - color: AppColors.textColor, weight: FontWeight.w600), + "Dr. John Doe".toText16(color: AppColors.textColor, weight: FontWeight.w600), SizedBox(height: 4.h), - "MBBS, MD - General Medicine".toText12( - color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + "MBBS, MD - General Medicine".toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), ], ), ), @@ -204,56 +237,54 @@ class TrackingScreen extends StatelessWidget { Widget mapForRequestedCoordinate(BuildContext context) { // return SizedBox.shrink(); - if (context.read().isGMSAvailable || - Platform.isIOS) - return GMSMap( - myLocationEnabled: false, - currentLocation: - context.read().getGMSLocation(), - onCameraMoved: (value) => context - .read() - .handleGMSMapCameraMoved(value)); - else + if (context.read().isGMSAvailable || Platform.isIOS) { + return GMSMap( + myLocationEnabled: false, + onCameraIdle: (){ + + }, + currentLocation: context.read().getGMSLocation(), + onCameraMoved: (value) => context.read().handleGMSMapCameraMoved(value)); + } else { return HMSMap( myLocationEnabled: false, - currentLocation: - context.read().getHMSLocation(), - onCameraMoved: (value) => context - .read() - .handleHMSMapCameraMoved(value)); + onCameraIdle: (){ + + }, + currentLocation: context.read().getHMSLocation(), + onCameraMoved: (value) => context.read().handleHMSMapCameraMoved(value)); + } } informationRegardingTrackingSection() { return Column( crossAxisAlignment: CrossAxisAlignment.start, - children: [ - currentStatus(), - SizedBox(height: 4.h), - "You will receive a call from HMG for confirmation ".toText12( - color: AppColors.greyTextColor, fontWeight: FontWeight.w500) - ], + children: [currentStatus(), SizedBox(height: 4.h), "You will receive a call from HMG for confirmation ".toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500)], ); } currentStatus() { - switch (state) { - case OrderTrackingState.waitingForCall: - case OrderTrackingState.dispactched: - return RichText( - text: TextSpan(children: [ - TextSpan( - text: "Please wait for the call".needTranslation, - style: TextStyle( - fontSize: 21.f, - fontWeight: FontWeight.w600, + if(state == null){ + return SizedBox.shrink(); + } + switch (state!) { + case OrderTrackingState.waitingForCall: + case OrderTrackingState.dispactched: + return RichText( + text: TextSpan(children: [ + TextSpan( + text: "Please wait for the call".needTranslation, + style: TextStyle( + fontSize: 21.f, + fontWeight: FontWeight.w600, color: AppColors.textColor, ), ), TextSpan( text: "...".needTranslation, style: TextStyle( - fontSize: 21.f, - fontWeight: FontWeight.w600, + fontSize: 21.f, + fontWeight: FontWeight.w600, color: AppColors.errorColor, ), ), @@ -265,38 +296,42 @@ class TrackingScreen extends StatelessWidget { TextSpan( text: "15:30".needTranslation, style: TextStyle( - fontSize: 21.f, - fontWeight: FontWeight.w600, + fontSize: 21.f, + fontWeight: FontWeight.w600, color: AppColors.textColor, ), ), TextSpan( text: " mins ".needTranslation, style: TextStyle( - fontSize: 21.f, - fontWeight: FontWeight.w600, + fontSize: 21.f, + fontWeight: FontWeight.w600, color: AppColors.errorColor, ), ), TextSpan( text: "to hospital".needTranslation, style: TextStyle( - fontSize: 21.f, - fontWeight: FontWeight.w600, + fontSize: 21.f, + fontWeight: FontWeight.w600, color: AppColors.textColor, ), ), ]), ); case OrderTrackingState.ended: - default: - return "Arrived" - .needTranslation - .toText21(color: AppColors.textColor, weight: FontWeight.w600); - } + + return "Arrived".needTranslation.toText21(color: AppColors.textColor, weight: FontWeight.w600); + case OrderTrackingState.failed: + case OrderTrackingState.cancel: + return SizedBox.shrink(); + } } - OrderTrackingId() { + orderTrackingId() { + if(state == OrderTrackingState.failed){ + return SizedBox.shrink(); + } return Container( padding: EdgeInsets.all(16.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -308,25 +343,27 @@ class TrackingScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 8.h, children: [ - "Req ID:1234567890" - .toText16(color: AppColors.textColor, weight: FontWeight.w600), + + Visibility(visible:order != null ,child: "Req ID: ${order?.iD}".toText16(color: AppColors.textColor, weight: FontWeight.w600)), Row( spacing: 8.h, children: [ - Chip("location name".needTranslation, AppAssets.location_pickup, - AppColors.blackBgColor), - Chip("hotel name ".needTranslation, AppAssets.hospital, - AppColors.blackBgColor), + Flexible(child: + chip(order?.pickupLocation??"", AppAssets.location_pickup, AppColors.blackBgColor), + + ), + Flexible(child: + chip(order?.dropOffLocation??"", AppAssets.hospital, AppColors.blackBgColor), + + ) ], ), - Chip("hotel name ".needTranslation, AppAssets.hospital, - AppColors.blackBgColor), ], ), ); } - Chip(String title, String iconString, Color iconColor) { + chip(String title, String iconString, Color iconColor) { return AppCustomChipWidget( labelText: title, icon: iconString, @@ -335,7 +372,7 @@ class TrackingScreen extends StatelessWidget { ); } - ContactSection() { + contactSection() { return Container( padding: EdgeInsets.all(16.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -350,11 +387,8 @@ class TrackingScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 4.h, children: [ - "Contact Rapid Response Team (RRT)".needTranslation.toText14( - color: AppColors.textColor, weight: FontWeight.w600), - "1223456789".needTranslation.toText12( - color: AppColors.primaryRedColor, - fontWeight: FontWeight.w500), + "Contact Rapid Response Team (RRT)".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w600), + "0115259555".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), SizedBox(height: 8.h), ], ), @@ -372,7 +406,11 @@ class TrackingScreen extends StatelessWidget { borderColor: Colors.transparent, height: 36.h, ), - ) + ).onPress((){ + launchUrl( + Uri.parse("tel://0115259555"), + ); + }) ], ), ); @@ -387,30 +425,37 @@ class TrackingScreen extends StatelessWidget { // ] // } - getTitle(OrderTrackingState state) { + getTitle(OrderTrackingState? state) { + if(state == null) + return "Failed".needTranslation.toText16(color: AppColors.textColor, weight: FontWeight.w600); + switch (state) { case OrderTrackingState.waitingForCall: - return "Confirmation Call".needTranslation - .toText16(color: AppColors.textColor, weight: FontWeight.w600); + return "Confirmation Call".needTranslation.toText16(color: AppColors.textColor, weight: FontWeight.w600); case OrderTrackingState.dispactched: - return "Pickup Up from Home".needTranslation - .toText16(color: AppColors.textColor, weight: FontWeight.w600); + return "Pickup Up from Home".needTranslation.toText16(color: AppColors.textColor, weight: FontWeight.w600); case OrderTrackingState.returning: - return " On The Way To Hospital".needTranslation - .toText16(color: AppColors.textColor, weight: FontWeight.w600); + return " On The Way To Hospital".needTranslation.toText16(color: AppColors.textColor, weight: FontWeight.w600); case OrderTrackingState.ended: - default: - return "Arrived at Hospital".needTranslation - .toText16(color: AppColors.textColor, weight: FontWeight.w600); + return "Arrived at Hospital".needTranslation.toText16(color: AppColors.textColor, weight: FontWeight.w600); + case OrderTrackingState.failed: + return "Failed".needTranslation.toText16(color: AppColors.textColor, weight: FontWeight.w600); + + case OrderTrackingState.cancel: + return "Order Cancel".needTranslation.toText16(color: AppColors.textColor, weight: FontWeight.w600); } } mapSection(BuildContext context) { - return SizedBox( + return SizedBox( height: 170.h, width: double.infinity, - child: ClipRRect( - borderRadius: BorderRadius.circular(20.h),child: mapForRequestedCoordinate(context)), + child: ClipRRect(borderRadius: BorderRadius.circular(20.h), child: mapForRequestedCoordinate(context)), ); } + + + openCancelOrderBottomSheet(BuildContext context){ + context.read().cancelOrder(order, shouldPop: true); + } } diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart b/lib/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart index c594888..71e589e 100644 --- a/lib/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart +++ b/lib/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart @@ -2,12 +2,17 @@ import 'package:easy_localization/easy_localization.dart' show tr, StringTranslateExtension; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/debouncer.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/emergency_services/emergency_services_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/models/resp_models/doctor_list_api_response.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart' show HospitalsModel; 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'; @@ -19,9 +24,18 @@ import 'package:provider/provider.dart'; class HospitalBottomSheetBody extends StatelessWidget { - final TextEditingController searchText = TextEditingController(); + final TextEditingController searchText ; + final Debouncer debouncer = Debouncer(milliseconds: 500); - HospitalBottomSheetBody({super.key}); + final int hmcCount; + final int hmgCount; + final List? displayList; + final FacilitySelection selectedFacility; + final Function(FacilitySelection) onFacilityClicked; + final Function(HospitalsModel) onHospitalClicked; + final Function(String) onHospitalSearch; + + HospitalBottomSheetBody({super.key, required this.hmcCount, required this.hmgCount, this.displayList, required this.selectedFacility, required this.onFacilityClicked, required this.onHospitalClicked, required this.onHospitalSearch, required this.searchText}); @override Widget build(BuildContext context) { @@ -29,16 +43,18 @@ class HospitalBottomSheetBody extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - TextInputWidget( labelText: LocaleKeys.search.tr(), hintText: LocaleKeys.searchHospital.tr(), controller: searchText, onChange: (value) { - + debouncer.run((){ + onHospitalSearch(value??""); + }); }, isEnable: true, prefix: null, + autoFocus: false, isBorderAllowed: false, keyboardType: TextInputType.text, @@ -51,10 +67,11 @@ class HospitalBottomSheetBody extends StatelessWidget { ), SizedBox(height: 24.h), TypeSelectionWidget( - hmcCount: "0", - hmgCount: "0", + selectedFacility:selectedFacility , + hmcCount: hmcCount.toString(), + hmgCount: hmgCount.toString(), onitemClicked: (selectedValue){ - + onFacilityClicked(selectedValue); }, ), SizedBox(height: 21.h), @@ -63,18 +80,18 @@ class HospitalBottomSheetBody extends StatelessWidget { child: ListView.separated( itemBuilder: (_, index) { - var hospital = null; + var hospital = displayList?[index]; return HospitalListItem( - hospitalData: hospital, - isLocationEnabled: false, + hospitalData: Utils.convertToPatientDoctorAppointmentList(hospital), + isLocationEnabled: true, ).onPress(() { - + onHospitalClicked(hospital!); });}, separatorBuilder: (_, __) => SizedBox( - height: 16.h, - ), - itemCount: 0, - )) + height: 16.h, + ), + itemCount: displayList?.length ?? 0, + )) ], ); } diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/ambulance_option_selection_bottomsheet.dart b/lib/presentation/emergency_services/call_ambulance/widgets/ambulance_option_selection_bottomsheet.dart index 564bd64..de20d1a 100644 --- a/lib/presentation/emergency_services/call_ambulance/widgets/ambulance_option_selection_bottomsheet.dart +++ b/lib/presentation/emergency_services/call_ambulance/widgets/ambulance_option_selection_bottomsheet.dart @@ -1,10 +1,14 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart' show AppAssets; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils; 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/PatientERTransportationMethod.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/transport_option_Item.dart' show TransportOptionItem; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:provider/provider.dart'; class AmbulanceOptionSelectionBottomSheet extends StatelessWidget { final Function onTap; @@ -12,40 +16,36 @@ class AmbulanceOptionSelectionBottomSheet extends StatelessWidget { const AmbulanceOptionSelectionBottomSheet({super.key, required this.onTap}); @override Widget build(BuildContext context) { - return Column( - spacing: 16.h, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + return Selector>( + selector: (_, model) => model.transportationOptions, + builder: (_, data, __) => Column( + spacing: 16.h, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: ListView.separated( + separatorBuilder: (_,__)=> SizedBox( + height: 16.h, + ), + itemCount: data.length??0, + itemBuilder: (_, index) => TransportOptionItem( + title: Utils.getTextWRTCurrentLanguage(data[index].text, data[index].textN), + subTitle: "", + firstIcon: AppAssets.location_pickup, + middleIcon: AppAssets.to_arrow, + lastIcon: AppAssets.hospital, + price: data[index].priceTotal.toString(), + onTap: () { + onTap(); + context.read().setTransportationOption(data[index]); + }, + ), + ), + ), - TransportOptionItem( - title: "Home to Hospital (One Way)", - subTitle: "Pickup from home location to hospital", - firstIcon: AppAssets.location_pickup, - middleIcon:AppAssets.to_arrow, - lastIcon: AppAssets.hospital, - price: "150 AED", - onTap: (){onTap();}, - ), - TransportOptionItem( - title: "Hospital to Home (One Way)", - subTitle: "Pickup from hospital to home", - lastIcon: AppAssets.location_pickup, - middleIcon:AppAssets.to_arrow, - firstIcon: AppAssets.hospital, - price: "150 AED", - onTap: (){onTap();}, - ), - TransportOptionItem( - title: "Home to Hospital (Two Way)", - subTitle: "Pick from home to hospital then drop off to home", - firstIcon: AppAssets.location_pickup, - middleIcon:AppAssets.dual_arrow, - lastIcon: AppAssets.hospital, - price: "150 AED", - onTap: (){onTap();}, - ), - SizedBox(height: 16.h,) - ], + ], + ), ); } } diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/appointment_bottom_sheet.dart b/lib/presentation/emergency_services/call_ambulance/widgets/appointment_bottom_sheet.dart new file mode 100644 index 0000000..6f5db92 --- /dev/null +++ b/lib/presentation/emergency_services/call_ambulance/widgets/appointment_bottom_sheet.dart @@ -0,0 +1,54 @@ +import 'package:flutter/cupertino.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/emergency_services/emergency_services_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/presentation/appointments/widgets/appointment_card.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_doctor_card.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/doctor_card.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:provider/provider.dart'; + +class AppointmentBottomSheet extends StatelessWidget{ + + + final Function(PatientAppointmentHistoryResponseModel) onAppointmentSelection; + final List list; + + const AppointmentBottomSheet({super.key, required this.onAppointmentSelection, required this.list}); + + @override + Widget build(BuildContext context) => getAppointList(list); + + Widget getAppointList( List list) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: ListView.separated( + padding: EdgeInsets.only(top: 24.h), + shrinkWrap: true, + physics: BouncingScrollPhysics(), + itemCount: list.length, + itemBuilder: (context, index) { + PatientAppointmentHistoryResponseModel item = list[index]; + return AppointmentDoctorCard( + renderWidgetForERDisplay: true, + patientAppointmentHistoryResponseModel: item, + onAskDoctorTap: () {}, + onCancelTap: () async {}, + onRescheduleTap: () async {}, + ).onPress((){ + onAppointmentSelection(item); + }); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + ), + SizedBox(height: 24.h), + ], + ); + } + + +} \ No newline at end of file diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart b/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart new file mode 100644 index 0000000..5d073d4 --- /dev/null +++ b/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart @@ -0,0 +1,172 @@ +import 'package:easy_localization/easy_localization.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/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/emergency_services/models/ambulance_direction.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:provider/provider.dart'; + +import '../../../../generated/locale_keys.g.dart' show LocaleKeys; + +class PickupLocation extends StatelessWidget { + final VoidCallback onTap; + const PickupLocation({super.key, required this.onTap}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Select Pickup Direction" + .needTranslation + .toText24(color: AppColors.textColor,isBold: true), + SizedBox( + height: 16.h, + ), + "Select Direction" + .needTranslation + .toText14(color: AppColors.textColor, weight: FontWeight.w600), + SizedBox( + height: 12.h, + ), + Selector( + selector: (context, viewModel) => viewModel.callingPlace, + builder: (context, value, _) { + return Column( + spacing: 12.h, + children: [ + RadioGroup( + groupValue: value, + onChanged: (value) { + context + .read() + .updateCallingPlace(value); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + spacing: 24.h, + children: [ + Row( + children: [ + Radio( + value: AmbulanceCallingPlace.TO_HOSPITAL, + groupValue: value, + activeColor: AppColors.primaryRedColor, + + fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), + ), + "To Hospital" + .needTranslation + .toText12(color: AppColors.textColor) + ], + ).onPress((){ + context + .read() + .updateCallingPlace(AmbulanceCallingPlace.TO_HOSPITAL); + }), + Row( + children: [ + Radio( + value: AmbulanceCallingPlace.FROM_HOSPITAL, + activeColor: AppColors.primaryRedColor, + + fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), + ), + "From Hospital" + .needTranslation + .toText12(color: AppColors.textColor) + ], + ).onPress((){ + context + .read() + .updateCallingPlace(AmbulanceCallingPlace.FROM_HOSPITAL); + }), + ], + ), + ), + Visibility( + visible: value == AmbulanceCallingPlace.TO_HOSPITAL, + child: Selector( + selector: (context, viewModel) => + viewModel.ambulanceDirection, + builder: (context, directionValue, _) { + return Column( + spacing: 12.h, + children: [ + "Select Way" + .needTranslation + .toText14(color: AppColors.textColor, weight: FontWeight.w600), + RadioGroup( + groupValue: directionValue, + onChanged: (value) { + context + .read() + .updateDirection(value); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + spacing: 24.h, + children: [ + Row( + children: [ + Radio( + value: AmbulanceDirection.ONE_WAY, + activeColor: AppColors.primaryRedColor, + + fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), + ), + "One Way" + .needTranslation + .toText12(color: AppColors.textColor) + ], + ).onPress((){ + context + .read() + .updateDirection(AmbulanceDirection.ONE_WAY); + }), + Row( + children: [ + Radio( + value: AmbulanceDirection.TWO_WAY, + // activeColor: AppColors.primaryRedColor, + + fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), + ), + "Two Way" + .needTranslation + .toText12(color: AppColors.textColor) + ], + ).onPress((){ + context + .read() + .updateDirection(AmbulanceDirection.TWO_WAY); + }), + ], + ), + ), + ], + ); + }), + ) + ], + ); + }), + SizedBox( + height: 16.h, + ), + CustomButton( + text: LocaleKeys.confirm.tr(context: context), + onPressed: onTap, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + iconColor: AppColors.whiteColor, + ), + ], + ); + } +} diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/transport_option_Item.dart b/lib/presentation/emergency_services/call_ambulance/widgets/transport_option_Item.dart index 2511824..9a959eb 100644 --- a/lib/presentation/emergency_services/call_ambulance/widgets/transport_option_Item.dart +++ b/lib/presentation/emergency_services/call_ambulance/widgets/transport_option_Item.dart @@ -48,7 +48,6 @@ class TransportOptionItem extends StatelessWidget { titleSection() { return Row( - children: [ Expanded( child: Column( @@ -56,8 +55,8 @@ class TransportOptionItem extends StatelessWidget { children: [ title.toText16( color: AppColors.textColor, weight: FontWeight.w600), - subTitle.toText12( - color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + // subTitle.toText12( + // color: AppColors.greyTextColor, fontWeight: FontWeight.w500), ], ), ), @@ -69,18 +68,23 @@ class TransportOptionItem extends StatelessWidget { headerSection() { return Row( + mainAxisAlignment: MainAxisAlignment.end, children: [ - Expanded( - child: Row( - children: [ - buildIcon(firstIcon), - Utils.buildSvgWithAssets( - icon: middleIcon, width: 24.h, height: 24.h).paddingAll(8.h), - buildIcon(lastIcon) - ], - ), + // Expanded( + // child: Row( + // children: [ + // buildIcon(firstIcon), + // Utils.buildSvgWithAssets( + // icon: middleIcon, width: 24.h, height: 24.h).paddingAll(8.h), + // buildIcon(lastIcon) + // ], + // ), + // ), + Utils.getPaymentAmountWithSymbol2( + num.tryParse(price) ?? 0.0, + fontSize: 18.f, + letterSpacing:-2 ), - price.toText18(color: AppColors.textColor, weight: FontWeight.w600) ], ); } diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/type_selection_widget.dart b/lib/presentation/emergency_services/call_ambulance/widgets/type_selection_widget.dart index c1ab8a8..3b8d8d7 100644 --- a/lib/presentation/emergency_services/call_ambulance/widgets/type_selection_widget.dart +++ b/lib/presentation/emergency_services/call_ambulance/widgets/type_selection_widget.dart @@ -12,10 +12,15 @@ import 'package:provider/provider.dart' show Consumer; class TypeSelectionWidget extends StatelessWidget { final String hmcCount; final String hmgCount; - final Function(String) onitemClicked; + final FacilitySelection selectedFacility; + final Function(FacilitySelection) onitemClicked; const TypeSelectionWidget( - {super.key, required this.hmcCount, required this.hmgCount, required this.onitemClicked}); + {super.key, + required this.hmcCount, + required this.hmgCount, + required this.onitemClicked, + required this.selectedFacility}); @override Widget build(BuildContext context) { @@ -28,51 +33,69 @@ class TypeSelectionWidget extends StatelessWidget { labelText: "All Facilities".needTranslation, shape: RoundedRectangleBorder( side: BorderSide( - color: AppColors.errorColor - , + color: selectedFacility == FacilitySelection.ALL + ? AppColors.errorColor + : AppColors.chipBorderColorOpacity20, width: 1, ), borderRadius: BorderRadius.circular(10)), backgroundColor: - AppColors.secondaryLightRedColor - , - textColor: AppColors.errorColor - , + selectedFacility == FacilitySelection.ALL + ?AppColors.secondaryLightRedColor: AppColors.whiteColor, + textColor: selectedFacility == FacilitySelection.ALL + ? AppColors.errorColor:AppColors.blackColor + , ).onPress((){ - onitemClicked(FacilitySelection.ALL.name); - }), - AppCustomChipWidget( - icon: AppAssets.hmg, - iconHasColor: false, - labelText: "Hospitals".needTranslation, - shape: RoundedRectangleBorder( - side: BorderSide( - color: AppColors.chipBorderColorOpacity20, - width: 1, - ), - borderRadius: BorderRadius.circular(10)), - backgroundColor: - AppColors.whiteColor, - textColor: AppColors.blackColor, - ).onPress((){ - onitemClicked(FacilitySelection.HMG.name); - }), - AppCustomChipWidget( - icon: AppAssets.hmc, - iconHasColor: false, - labelText: "Medical Centers".needTranslation, - shape: RoundedRectangleBorder( - side: BorderSide( - color:AppColors.chipBorderColorOpacity20, - width: 1, - ), - borderRadius: BorderRadius.circular(10)), - backgroundColor: - AppColors.whiteColor, - textColor: AppColors.blackColor, - ).onPress((){ - onitemClicked(FacilitySelection.HMC.name); + onitemClicked(FacilitySelection.ALL); }), + Visibility( + visible: hmgCount != "0", + child: AppCustomChipWidget( + icon: AppAssets.hmg, + iconHasColor: false, + labelText: "Hospitals".needTranslation, + shape: RoundedRectangleBorder( + side: BorderSide( + color: selectedFacility == FacilitySelection.HMG + ? AppColors.errorColor + : AppColors.chipBorderColorOpacity20, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + backgroundColor: + selectedFacility == FacilitySelection.HMG + ?AppColors.secondaryLightRedColor: AppColors.whiteColor, + textColor: selectedFacility == FacilitySelection.HMG + ? AppColors.errorColor + : AppColors.blackColor, + ).onPress((){ + onitemClicked(FacilitySelection.HMG); + }), + ), + Visibility( + visible: hmcCount != "0", + child: AppCustomChipWidget( + icon: AppAssets.hmc, + iconHasColor: false, + labelText: "Medical Centers".needTranslation, + shape: RoundedRectangleBorder( + side: BorderSide( + color: selectedFacility == FacilitySelection.HMC + ? AppColors.errorColor + : AppColors.chipBorderColorOpacity20, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + backgroundColor: + selectedFacility == FacilitySelection.HMC + ?AppColors.secondaryLightRedColor: AppColors.whiteColor, + textColor: selectedFacility == FacilitySelection.HMC + ? AppColors.errorColor + : AppColors.blackColor, + ).onPress((){ + onitemClicked(FacilitySelection.HMC); + }), + ), ], ); } diff --git a/lib/presentation/emergency_services/emergency_services_page.dart b/lib/presentation/emergency_services/emergency_services_page.dart index ddea6fd..3cbce23 100644 --- a/lib/presentation/emergency_services/emergency_services_page.dart +++ b/lib/presentation/emergency_services/emergency_services_page.dart @@ -8,15 +8,19 @@ 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/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'; 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:lottie/lottie.dart'; import 'package:provider/provider.dart'; @@ -33,8 +37,10 @@ class EmergencyServicesPage extends StatelessWidget { locationUtils = getIt.get(); locationUtils!.isShowConfirmDialog = true; return CollapsingListView( - title: "Emergency Services".needTranslation, - requests: () {}, + title: LocaleKeys.emergencyServices.tr(), + requests: () { + Navigator.of(context).push(CustomPageRoute(page: ErHistoryListing(), direction: AxisDirection.up)); + }, child: Padding( padding: EdgeInsets.all(24.h), child: Column( @@ -56,7 +62,7 @@ class EmergencyServicesPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ "Call Ambulance".needTranslation.toText16(isBold: true, color: AppColors.blackColor), - "Request and ambulance in emergency from home or hospital".needTranslation.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + "Request an ambulance in emergency from home or hospital".needTranslation.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), ], ), ), @@ -64,6 +70,8 @@ class EmergencyServicesPage extends StatelessWidget { Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, width: 13.h, height: 13.h), ], ).onPress(() { + + showCommonBottomSheetWithoutHeight( context, child: Container( @@ -114,23 +122,8 @@ class EmergencyServicesPage extends StatelessWidget { onPressed: () async { // Navigator.of(context).pop(); - showCommonBottomSheetWithoutHeight( - title: - "Transport Options".needTranslation, - context, - child: AmbulanceOptionSelectionBottomSheet( - onTap: () { - Navigator.of(context).pop(); - context - .read() - .navigateTOAmbulancePage(); - }), - isFullScreen: false, - isCloseButtonVisible: true, - hasBottomPadding: false, - backgroundColor: AppColors.bottomSheetBgColor, - callBackFunc: () {}, - ); + await emergencyServicesViewModel.getTransportationOption(); + openTranportationSelectionBottomSheet(context); }, backgroundColor: AppColors.whiteColor, borderColor: AppColors.whiteColor, @@ -233,7 +226,7 @@ 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), SizedBox(height: 8.h), - "Confirmation".needTranslation.toText28(color: AppColors.whiteColor, isBold: true), + 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), SizedBox(height: 24.h), @@ -241,16 +234,108 @@ class EmergencyServicesPage extends StatelessWidget { text: LocaleKeys.confirm.tr(context: context), onPressed: () async { Navigator.of(context).pop(); - showCommonBottomSheetWithoutHeight( - title: "Rapid Response Team (RRT)".needTranslation, - context, - child: RrtRequestTypeSelect(), - isFullScreen: false, - isCloseButtonVisible: true, - hasBottomPadding: false, - backgroundColor: AppColors.bottomSheetBgColor, - callBackFunc: () {}, - ); + + LoaderBottomSheet.showLoader(); + await emergencyServicesViewModel.getRRTProcedures(onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + title: "Rapid Response Team (RRT)".needTranslation, + context, + child: RrtRequestTypeSelect(), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () {}, + ); + }); + }, + backgroundColor: AppColors.whiteColor, + borderColor: AppColors.whiteColor, + textColor: AppColors.primaryRedColor, + icon: AppAssets.checkmark_icon, + iconColor: AppColors.primaryRedColor, + ), + SizedBox(height: 8.h), + ], + ), + ), + ), + isFullScreen: false, + isCloseButtonVisible: false, + hasBottomPadding: false, + backgroundColor: AppColors.primaryRedColor, + callBackFunc: () {}, + ); + }), + ), + SizedBox(height: 16.h), + Container( + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.rrt_icon, width: 40.h, height: 40.h), + SizedBox(width: 12.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "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), + ], + ), + ), + SizedBox(width: 12.h), + Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, width: 13.h, height: 13.h), + ], + ).onPress(() { + showCommonBottomSheetWithoutHeight( + context, + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.primaryRedColor, + borderRadius: 24.h, + ), + child: Padding( + padding: EdgeInsets.all(24.h), + 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 make ER Check-In?".needTranslation.toText14(color: AppColors.whiteColor, weight: FontWeight.w500), + SizedBox(height: 24.h), + CustomButton( + text: LocaleKeys.confirm.tr(context: context), + onPressed: () async { + Navigator.of(context).pop(); + LoaderBottomSheet.showLoader(loadingText: "Checking your ER Appointment status...".needTranslation); + await context.read().checkPatientERAdvanceBalance(onSuccess: (dynamic response) { + LoaderBottomSheet.hideLoader(); + context.read().navigateToEROnlineCheckIn(); + }); }, backgroundColor: AppColors.whiteColor, borderColor: AppColors.whiteColor, @@ -276,4 +361,68 @@ class EmergencyServicesPage extends StatelessWidget { ), ); } + + openPickupDetailsBottomSheet(BuildContext context){ + showCommonBottomSheetWithoutHeight( + onCloseClicked: (){ + context + .read() + .flushPickupInformation(); + }, + titleWidget: Transform.flip( + flipX: emergencyServicesViewModel.isArabic ? true : false, + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrow_back, + iconColor: Color(0xff2B353E), + fit: BoxFit.contain, + ), + ).onPress(() { + context + .read() + .flushPickupInformation(); + Navigator.pop(context); + openTranportationSelectionBottomSheet(context); + }), + context, + 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) { + showCommonBottomSheetWithoutHeight( + title: "Transport Options".needTranslation, + context, + child: SizedBox( + height: 400.h, + child: AmbulanceOptionSelectionBottomSheet( + onTap: () { + Navigator.of(context).pop(); + openPickupDetailsBottomSheet(context); + // context + // .read() + // .navigateTOAmbulancePage(); + }), + ), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () {}, + ); + } + } } diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart new file mode 100644 index 0000000..0dfd081 --- /dev/null +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart @@ -0,0 +1,181 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_nfc_kit/flutter_nfc_kit.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/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/er_online_checkin/er_online_checkin_select_checkin_bottom_sheet.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/loader/bottomsheet_loader.dart'; +import 'package:hmg_patient_app_new/widgets/nfc/nfc_reader_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:provider/provider.dart'; + +import '../call_ambulance/widgets/HospitalBottomSheetBody.dart'; + +class ErOnlineCheckinHome extends StatelessWidget { + ErOnlineCheckinHome({super.key}); + + late EmergencyServicesViewModel emergencyServicesViewModel; + bool _supportsNFC = false; + + @override + Widget build(BuildContext context) { + emergencyServicesViewModel = Provider.of(context, listen: false); + FlutterNfcKit.nfcAvailability.then((value) { + _supportsNFC = (value == NFCAvailability.available); + }); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Emergency Check-In".needTranslation, + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.all(24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.immediate_service_icon, width: 58.h, height: 58.h), + SizedBox(width: 18.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Online Check-In".needTranslation.toText18(color: AppColors.textColor, isBold: true), + "This service lets patients to register their ER appointment prior to arrival.".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ), + ], + ), + ], + ), + ), + ), + ), + ), + CustomButton( + text: emergencyServicesViewModel.patientHasAdvanceERBalance ? LocaleKeys.checkinOption.tr() : LocaleKeys.bookAppo.tr(), + onPressed: () async { + if (emergencyServicesViewModel.patientHasAdvanceERBalance) { + Future.delayed(const Duration(milliseconds: 500), () { + showNfcReader(context, onNcfScan: (String nfcId) { + Future.delayed(const Duration(milliseconds: 100), () async { + print(nfcId); + LoaderBottomSheet.showLoader(loadingText: "Processing check-in...".needTranslation); + await emergencyServicesViewModel.getProjectIDFromNFC( + nfcCode: nfcId, + onSuccess: (value) async { + await emergencyServicesViewModel.autoGenerateInvoiceERClinic( + projectID: value, + onSuccess: (value) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight(context, + title: LocaleKeys.onlineCheckIn.tr(), + child: Utils.getSuccessWidget(loadingText: "Your ER Online Check-In has been successfully done. Please proceed to the waiting area.".needTranslation), + callBackFunc: () { + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }, isFullScreen: false); + }, + onError: (errMessage) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Unexpected error occurred during check-in. Please contact support.".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, + onError: (err) {}); + }); + }, onCancel: () {}); + }); + // showCommonBottomSheetWithoutHeight(context, + // title: LocaleKeys.onlineCheckIn.tr(), + // child: ErOnlineCheckinSelectCheckinBottomSheet( + // projectID: 15, + // ), + // callBackFunc: () {}, + // isFullScreen: false); + } else { + LoaderBottomSheet.showLoader(loadingText: "Fetching hospitals list...".needTranslation); + await context.read().getProjects(); + LoaderBottomSheet.hideLoader(); + //Project Selection Dropdown + showHospitalBottomSheet(context); + } + }, + backgroundColor: emergencyServicesViewModel.patientHasAdvanceERBalance ? AppColors.alertColor : AppColors.primaryRedColor, + borderColor: emergencyServicesViewModel.patientHasAdvanceERBalance ? AppColors.alertColor : AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 10.r, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: emergencyServicesViewModel.patientHasAdvanceERBalance ? AppAssets.appointment_checkin_icon : AppAssets.bookAppoBottom, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ).paddingSymmetrical(24.h, 24.h), + ], + ), + ); + } + + showHospitalBottomSheet(BuildContext context) { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.selectHospital.tr(), + context, + child: Consumer( + builder: (_, vm, __) => HospitalBottomSheetBody( + displayList: vm.displayList, + onFacilityClicked: (value) { + vm.setSelectedFacility(value); + vm.getDisplayList(); + }, + onHospitalClicked: (hospital) async { + Navigator.pop(context); + vm.setSelectedHospital(hospital); + LoaderBottomSheet.showLoader(loadingText: "Fetching payment information...".needTranslation); + await vm.getPatientERPaymentInformation(onSuccess: (response) { + LoaderBottomSheet.hideLoader(); + vm.navigateToEROnlineCheckInPaymentPage(); + }); + }, + onHospitalSearch: (value) { + vm.searchHospitals(value ?? ""); + }, + selectedFacility: vm.selectedFacility, + hmcCount: vm.hmcCount, + hmgCount: vm.hmgCount, + searchText: vm.searchController, + ), + ), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () {}, + ); + } +} diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart new file mode 100644 index 0000000..3aa975d --- /dev/null +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart @@ -0,0 +1,177 @@ +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/emergency_services/emergency_services_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/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:provider/provider.dart'; + +class ErOnlineCheckinPaymentDetailsPage extends StatelessWidget { + ErOnlineCheckinPaymentDetailsPage({super.key}); + + late AppState appState; + late EmergencyServicesViewModel emergencyServicesViewModel; + + @override + Widget build(BuildContext context) { + appState = getIt.get(); + emergencyServicesViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Emergency Check-In".needTranslation, + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.all(24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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: [ + "ER Visit Details".needTranslation.toText18(color: AppColors.textColor, isBold: true), + SizedBox(height: 24.h), + Row( + children: [ + "${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}".toText14(color: AppColors.textColor, isBold: true), + ], + ), + SizedBox(height: 12.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.w, + runSpacing: 6.h, + children: [ + AppCustomChipWidget( + labelText: "File No.: ${appState.getAuthenticatedUser()!.patientId!.toString()}", + labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), + ), + AppCustomChipWidget( + labelText: "ER Clinic".needTranslation, + labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), + ), + AppCustomChipWidget( + labelText: emergencyServicesViewModel.selectedHospital!.name, + labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), + ), + AppCustomChipWidget( + icon: AppAssets.calendar, + labelText: DateUtil.formatDateToDate(DateTime.now(), false), + labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w), + ), + ], + ), + SizedBox(height: 12.h), + ], + ), + ), + ) + ], + ), + ), + ), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: true, + ), + child: SizedBox( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Amount before tax".needTranslation.toText18(isBold: true), + Utils.getPaymentAmountWithSymbol(emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShare.toString().toText16(isBold: true), AppColors.blackColor, 13, + isSaudiCurrency: true), + ], + ), + SizedBox(height: 4.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded(child: "".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor)), + "VAT 15% (${emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientTaxAmount})" + .needTranslation + .toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -1), + ], + ), + SizedBox(height: 18.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: 150.h, + child: Utils.getPaymentMethods(), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Utils.getPaymentAmountWithSymbol( + emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax.toString().toText24(isBold: true), AppColors.blackColor, 17, + isSaudiCurrency: true), + ], + ), + ], + ) + ], + ).paddingOnly(left: 16.h, top: 24.h, right: 16.h, bottom: 0.h), + CustomButton( + text: LocaleKeys.payNow.tr(), + onPressed: () { + Navigator.of(context).push( + CustomPageRoute(page: ErOnlineCheckinPaymentPage()), + ); + }, + backgroundColor: AppColors.infoColor, + borderColor: AppColors.infoColor.withOpacity(0.01), + textColor: AppColors.whiteColor, + fontSize: 16.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 56.h, + icon: AppAssets.appointment_pay_icon, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ).paddingSymmetrical(16.h, 24.h), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart new file mode 100644 index 0000000..a3524fd --- /dev/null +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_payment_page.dart @@ -0,0 +1,510 @@ +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/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/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/insurance/insurance_home_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/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'; +import 'package:smooth_corner/smooth_corner.dart'; + +class ErOnlineCheckinPaymentPage extends StatefulWidget { + ErOnlineCheckinPaymentPage({super.key}); + + @override + State createState() => _ErOnlineCheckinPaymentPageState(); +} + +class _ErOnlineCheckinPaymentPageState extends State { + late PayfortViewModel payfortViewModel; + late EmergencyServicesViewModel emergencyServicesViewModel; + + late AppState appState; + + MyInAppBrowser? browser; + + String selectedPaymentMethod = ""; + + String transID = ""; + + bool isShowTamara = false; + + String tamaraPaymentStatus = ""; + + String tamaraOrderID = ""; + + @override + void initState() { + scheduleMicrotask(() { + payfortViewModel.initPayfortViewModel(); + // payfortViewModel.getTamaraInstallmentsDetails().then((val) { + // if (emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax! >= payfortViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && + // emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax! <= payfortViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) { + // setState(() { + // isShowTamara = true; + // }); + // } + // }); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + appState = getIt.get(); + payfortViewModel = Provider.of(context, listen: false); + emergencyServicesViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Emergency Check-In".needTranslation, + child: SingleChildScrollView( + child: Column( + children: [ + SizedBox(height: 24.h), + 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), + SizedBox(height: 16.h), + "Mada".needTranslation.toText16(isBold: true), + ], + ), + SizedBox(width: 8.h), + const Spacer(), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 40.h, + height: 40.h, + fit: BoxFit.contain, + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + selectedPaymentMethod = "MADA"; + openPaymentURL("mada"); + }), + SizedBox(height: 16.h), + 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), + ], + ), + SizedBox(height: 16.h), + "Visa or Mastercard".needTranslation.toText16(isBold: true), + ], + ), + SizedBox(width: 8.h), + const Spacer(), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 40.h, + height: 40.h, + fit: BoxFit.contain, + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + selectedPaymentMethod = "VISA"; + openPaymentURL("visa"); + }), + SizedBox(height: 16.h), + isShowTamara + ? 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.tamara_en, width: 72.h, height: 25.h), + SizedBox(height: 16.h), + "Tamara".needTranslation.toText16(isBold: true), + ], + ), + SizedBox(width: 8.h), + const Spacer(), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 40.h, + height: 40.h, + fit: BoxFit.contain, + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + selectedPaymentMethod = "TAMARA"; + openPaymentURL("tamara"); + }) + : SizedBox.shrink(), + ], + ), + ), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Consumer(builder: (context, payfortVM, child) { + //TODO: Need to add loading state & animation for Apple Pay Configuration + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.isCash ?? true) + ? Container( + height: 50.h, + decoration: ShapeDecoration( + color: AppColors.secondaryLightRedBorderColor, + shape: SmoothRectangleBorder( + borderRadius: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), + smoothness: 1, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "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: () { + Navigator.of(context).push( + CustomPageRoute( + page: InsuranceHomePage(), + ), + ); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.secondaryLightRedBorderColor, + textColor: AppColors.whiteColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(15, 0, 15, 0), + height: 30.h, + ).paddingSymmetrical(24.h, 0.h), + ], + ), + ) + : const SizedBox(), + SizedBox(height: 24.h), + "Total amount to pay".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 17.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Amount before tax".needTranslation.toText14(isBold: true), + Utils.getPaymentAmountWithSymbol(emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShare.toString().toText16(isBold: true), AppColors.blackColor, 13, + isSaudiCurrency: true), + ], + ).paddingSymmetrical(24.h, 0.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor), + Utils.getPaymentAmountWithSymbol( + emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientTaxAmount.toString().toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13, + isSaudiCurrency: true), + ], + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 17.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "".needTranslation.toText14(isBold: true), + Utils.getPaymentAmountWithSymbol(emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax.toString().toText24(isBold: true), AppColors.blackColor, 17, + isSaudiCurrency: true), + ], + ).paddingSymmetrical(24.h, 0.h), + Platform.isIOS + ? Utils.buildSvgWithAssets( + icon: AppAssets.apple_pay_button, + width: 200.h, + height: 80.h, + fit: BoxFit.contain, + ).paddingSymmetrical(24.h, 0.h).onPress(() { + if (Utils.havePrivilege(103)) { + startApplePay(); + } else { + openPaymentURL("ApplePay"); + } + }) + : SizedBox(height: 12.h), + SizedBox(height: 12.h), + ], + ); + }), + ), + ], + ), + ); + } + + openPaymentURL(String paymentMethod) { + browser = MyInAppBrowser(onExitCallback: onBrowserExit, onLoadStartCallback: onBrowserLoadStart, context: context); + transID = Utils.getAdvancePaymentTransID( + emergencyServicesViewModel.selectedHospital!.iD, + appState.getAuthenticatedUser()!.patientId!, + ); + + //TODO: Need to pass dynamic params to the payment request instead of static values + browser?.openPaymentBrowser( + emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!, + "ER Online Check-In Payment", + transID, + emergencyServicesViewModel.selectedHospital!.iD.toString(), + "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", + selectedPaymentMethod, + appState.getAuthenticatedUser()!.patientType.toString(), + "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", + appState.getAuthenticatedUser()!.patientId.toString(), + appState.getAuthenticatedUser()!, + browser!, + false, + "3", + "", + context, + null, + 0, + 10, + 0, + "3"); + } + + startApplePay() async { + // showCommonBottomSheet(context, + // child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); + LoaderBottomSheet.showLoader(); + transID = Utils.getAdvancePaymentTransID( + emergencyServicesViewModel.selectedHospital!.iD, + appState.getAuthenticatedUser()!.patientId!, + ); + + ApplePayInsertRequest applePayInsertRequest = ApplePayInsertRequest(); + + await payfortViewModel.getPayfortConfigurations(serviceId: ServiceTypeEnum.erOnlineCheckIn.getIdFromServiceEnum(), projectId: emergencyServicesViewModel.selectedHospital!.iD); + + applePayInsertRequest.clientRequestID = transID; + applePayInsertRequest.clinicID = 10; + + 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 = emergencyServicesViewModel.selectedHospital!.iD.toString(); + applePayInsertRequest.serviceID = ServiceTypeEnum.erOnlineCheckIn.getIdFromServiceEnum().toString(); + applePayInsertRequest.channelID = 3; + applePayInsertRequest.patientID = appState.getAuthenticatedUser()!.patientId.toString(); + applePayInsertRequest.patientTypeID = appState.getAuthenticatedUser()!.patientType; + applePayInsertRequest.patientOutSA = appState.getAuthenticatedUser()!.outSa; + applePayInsertRequest.appointmentDate = null; + applePayInsertRequest.appointmentNo = 0; + applePayInsertRequest.orderDescription = "ER Online Check-In Payment"; + applePayInsertRequest.liveServiceID = "0"; + applePayInsertRequest.latitude = "0.0"; + applePayInsertRequest.longitude = "0.0"; + applePayInsertRequest.amount = emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!.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 = ""; + + //TODO: Need to pass dynamic params to the Apple Pay instead of static values + await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest).then((value) { + payfortViewModel.paymentWithApplePay( + customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}", + // customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress, + customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", + orderDescription: "Appointment Payment", + orderAmount: double.parse(emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!.toString()), + 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 { + log("successResult: ${successResult.responseMessage.toString()}"); + selectedPaymentMethod = successResult.paymentOption ?? "VISA"; + checkPaymentStatus(); + }, + ); + }); + } + + void checkPaymentStatus() async { + LoaderBottomSheet.showLoader(loadingText: "Checking payment status, Please wait...".needTranslation); + await payfortViewModel.checkPaymentStatus( + transactionID: transID, + onSuccess: (apiResponse) async { + print(apiResponse.data); + if (payfortViewModel.payfortCheckPaymentStatusResponseModel!.responseMessage!.toLowerCase() == "success") { + if (emergencyServicesViewModel.isERBookAppointment) { + await emergencyServicesViewModel.ER_CreateAdvancePayment( + paymentMethodName: selectedPaymentMethod, + paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!, + onSuccess: (value) async { + await emergencyServicesViewModel.addAdvanceNumberRequest( + advanceNumber: value, + paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!, + appointmentNo: "0", + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + if (emergencyServicesViewModel.isERBookAppointment) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getSuccessWidget(loadingText: "Your appointment has been booked successfully. Please perform Check-In once you arrive at the hospital.".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } else {} + }); + }); + } else {} + } else { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }); + } + + onBrowserLoadStart(String url) { + print("onBrowserLoadStart"); + print(url); + + if (selectedPaymentMethod == "tamara") { + if (Platform.isAndroid) { + Uri uri = new Uri.dataFromString(url); + tamaraPaymentStatus = uri.queryParameters['status']!; + tamaraOrderID = uri.queryParameters['AuthorizePaymentId']!; + } else { + Uri uri = new Uri.dataFromString(url); + tamaraPaymentStatus = uri.queryParameters['paymentStatus']!; + tamaraOrderID = uri.queryParameters['orderId']!; + } + } + + // if(selectedPaymentMethod != "TAMARA") { + MyInAppBrowser.successURLS.forEach((element) { + if (url.contains(element)) { + browser?.close(); + MyInAppBrowser.isPaymentDone = true; + return; + } + }); + // } + + // if(selectedPaymentMethod != "TAMARA") { + MyInAppBrowser.errorURLS.forEach((element) { + if (url.contains(element)) { + browser?.close(); + MyInAppBrowser.isPaymentDone = false; + return; + } + }); + // } + } + + onBrowserExit(bool isPaymentMade) async { + checkPaymentStatus(); + } +} diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_select_checkin_bottom_sheet.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_select_checkin_bottom_sheet.dart new file mode 100644 index 0000000..0b22417 --- /dev/null +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_select_checkin_bottom_sheet.dart @@ -0,0 +1,169 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_nfc_kit/flutter_nfc_kit.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/common_models/privilege/ProjectDetailListModel.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/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/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/theme/colors.dart'; +import 'package:barcode_scan2/barcode_scan2.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/nfc/nfc_reader_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; + +class ErOnlineCheckinSelectCheckinBottomSheet extends StatelessWidget { + ErOnlineCheckinSelectCheckinBottomSheet({super.key, required this.projectID}); + + bool _supportsNFC = false; + int projectID = 0; + + late LocationUtils locationUtils; + late AppState appState; + ProjectDetailListModel projectDetailListModel = ProjectDetailListModel(); + + @override + Widget build(BuildContext context) { + appState = getIt.get(); + FlutterNfcKit.nfcAvailability.then((value) { + _supportsNFC = (value == NFCAvailability.available); + }); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + checkInOptionCard( + AppAssets.checkin_location_icon, + "Live Location".needTranslation, + "Verify your location to be at hospital to check in".needTranslation, + ).onPress(() { + // locationUtils = LocationUtils( + // isShowConfirmDialog: false, + // navigationService: myAppointmentsViewModel.navigationService, + // appState: myAppointmentsViewModel.appState, + // ); + locationUtils.getCurrentLocation(onSuccess: (value) { + projectDetailListModel = Utils.getProjectDetailObj(appState, projectID); + double dist = Utils.distance(value.latitude, value.longitude, double.parse(projectDetailListModel.latitude!), double.parse(projectDetailListModel.longitude!)).ceilToDouble() * 1000; + print(dist); + if (dist <= projectDetailListModel.geofenceRadius!) { + sendCheckInRequest(projectDetailListModel.checkInQrCode!, context); + } else { + showCommonBottomSheetWithoutHeight(context, + title: "Error".needTranslation, + child: Utils.getErrorWidget(loadingText: "Please ensure you're within the hospital location to perform online check-in.".needTranslation), callBackFunc: () { + Navigator.of(context).pop(); + }, isFullScreen: false); + } + }); + }), + SizedBox(height: 16.h), + checkInOptionCard( + AppAssets.checkin_nfc_icon, + "NFC (Near Field Communication)".needTranslation, + "Scan your phone via NFC board to check in".needTranslation, + ).onPress(() { + Future.delayed(const Duration(milliseconds: 500), () { + showNfcReader(context, onNcfScan: (String nfcId) { + Future.delayed(const Duration(milliseconds: 100), () { + sendCheckInRequest(nfcId, context); + }); + }, onCancel: () {}); + }); + }), + SizedBox(height: 16.h), + checkInOptionCard( + AppAssets.checkin_qr_icon, + "QR Code".needTranslation, + "Scan QR code with your camera to check in".needTranslation, + ).onPress(() async { + String onlineCheckInQRCode = (await BarcodeScanner.scan().then((value) => value.rawContent)); + if (onlineCheckInQRCode != "") { + sendCheckInRequest(onlineCheckInQRCode, context); + } else {} + }), + ], + ); + } + + Widget checkInOptionCard(String icon, String title, String subTitle) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: icon, width: 40.h, height: 40.h, fit: BoxFit.fill), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.toText16(isBold: true, color: AppColors.textColor), + subTitle.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + ], + ), + ), + 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, + ), + ), + ], + ), + ], + ).paddingAll(16.h), + ); + } + + void sendCheckInRequest(String scannedCode, BuildContext context) async { + showCommonBottomSheet(context, + child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); + // await myAppointmentsViewModel.sendCheckInNfcRequest( + // patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + // scannedCode: scannedCode, + // checkInType: 2, + // onSuccess: (apiResponse) { + // Navigator.of(context).pop(); + // showCommonBottomSheetWithoutHeight(context, title: "Success".needTranslation, child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), callBackFunc: () { + // Navigator.of(context).pop(); + // Navigator.pushAndRemoveUntil( + // context, + // CustomPageRoute( + // page: LandingNavigation(), + // ), + // (r) => false); + // Navigator.of(context).push( + // CustomPageRoute(page: MyAppointmentsPage()), + // ); + // }, isFullScreen: false); + // }, + // onError: (error) { + // Navigator.of(context).pop(); + // showCommonBottomSheetWithoutHeight(context, title: "Error".needTranslation, child: Utils.getErrorWidget(loadingText: error), callBackFunc: () { + // Navigator.of(context).pop(); + // }, isFullScreen: false); + // }, + // ); + } +} diff --git a/lib/presentation/emergency_services/history/er_history_listing.dart b/lib/presentation/emergency_services/history/er_history_listing.dart new file mode 100644 index 0000000..e5dd1e4 --- /dev/null +++ b/lib/presentation/emergency_services/history/er_history_listing.dart @@ -0,0 +1,62 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.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/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/widgets/appbar/collapsing_list_view.dart'; +import 'package:provider/provider.dart'; + +class ErHistoryListing extends StatelessWidget { + @override + Widget build(BuildContext context) { + 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]), + ) + ), + ),] + ).paddingAll(16.h) + + )) + + ), + + + Visibility( + visible: context + .read() + .orders + ?.isEmpty == true, + child: Utils.getNoDataWidget(context, + noDataText: "You don't have any history" + .needTranslation)), + ], + ), + ); + } +} diff --git a/lib/presentation/emergency_services/history/widget/RequestStatus.dart b/lib/presentation/emergency_services/history/widget/RequestStatus.dart new file mode 100644 index 0000000..de95bdc --- /dev/null +++ b/lib/presentation/emergency_services/history/widget/RequestStatus.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; + +class RequestStatus extends StatelessWidget { + final int status; + + const RequestStatus({super.key, required this.status}); + + @override + Widget build(BuildContext context) { + return AppCustomChipWidget( + labelText: getTitle(status), + backgroundColor: getBackgroundColor(status), + textColor: getTextColor(status), + ); + } + + String? getTitle(int status) { + switch (status) { + case 1: //pending + case 2: //processing + return "Under Processing".needTranslation; + case 3: //completed + return "Completed".needTranslation; + break; + case 4: //cancel + case 6: + case 7: + return "Canceled by patient".needTranslation; + break; + } + return null; + } + + Color? getBackgroundColor(int status) { + switch (status) { + case 1: //pending + case 2: //processing + case 3: //completed + return AppColors.switchBackgroundColor; + break; + case 4: //cancel + case 6: + case 7: + return AppColors.lightRedButtonColor; + break; + } + return null; + } + + Color? getTextColor(int status) { + switch (status) { + case 1: //pending + case 2: + return AppColors.successColor;//processing + case 3: //completed + case 4: //cancel + case 6: + case 7: + return AppColors.errorColor; + break; + } + return null; + } +} diff --git a/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart b/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart new file mode 100644 index 0000000..5f4c8b2 --- /dev/null +++ b/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart @@ -0,0 +1,81 @@ +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/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 AmbulanceHistoryItem extends StatelessWidget { + final AmbulanceRequestOrdersModel order; + + const AmbulanceHistoryItem({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("Ambulance".needTranslation, AppAssets.ambulance, AppColors.blackBgColor), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Flexible(child: chip(order?.dropOffLocation ?? "", (order.dropOffLocation?.toLowerCase().contains("hospital") == true)?AppAssets.hospital:AppAssets.location_pickup, AppColors + .blackBgColor),) + ], + ), + 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, + ), + ], + ).paddingAll(16.h), + ).paddingOnly(bottom: 16.h); + } + + chip(String title, String iconString, Color iconColor) { + return AppCustomChipWidget( + labelText: title, + icon: iconString, + iconColor: iconColor, + iconSize: 14.h, + ); + } + + openCancelOrderBottomSheet(BuildContext context) { + context.read().cancelOrder(order); + } +} diff --git a/lib/presentation/emergency_services/nearest_er_page.dart b/lib/presentation/emergency_services/nearest_er_page.dart index 8138f57..13373f1 100644 --- a/lib/presentation/emergency_services/nearest_er_page.dart +++ b/lib/presentation/emergency_services/nearest_er_page.dart @@ -7,7 +7,7 @@ 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/model/resp_model/ProjectAvgERWaitingTime.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/ProjectAvgERWaitingTime.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/widgets/nearestERItem.dart' show NearestERItem; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; diff --git a/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart b/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart new file mode 100644 index 0000000..559a0a5 --- /dev/null +++ b/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart @@ -0,0 +1,118 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart' + show AnimationConfiguration, SlideAnimation, FadeInAnimation; +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/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/debouncer.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/location/GeocodeResponse.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/widgets/input_widget.dart'; +import 'package:provider/provider.dart'; + +import '../../../theme/colors.dart'; +import '../../appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart'; + +class LocationInputBottomSheet extends StatelessWidget { + final Debouncer debouncer = Debouncer(milliseconds: 500); + + LocationInputBottomSheet({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextInputWidget( + labelText: LocaleKeys.search.tr(), + hintText: "Search Location".needTranslation, + controller: TextEditingController(), + onChange: (value){ + debouncer.run(() { + context.read().updateSearchQuery(value); + }); + }, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + isAllowLeadingIcon: true, + selectionType: SelectionTypeEnum.search, + padding: EdgeInsets.symmetric( + vertical: 10.h, + horizontal: 15.h, + ), + ), + SizedBox(height: 24.h), + Selector>( + selector: (_, vm) => vm.predictions ?? [], + builder: (context, predictions, _) { + if (predictions.isEmpty) return SizedBox.shrink(); + return Expanded( + child: ListView.separated( + separatorBuilder: (_,__)=>SizedBox(height: 16.h,), + itemCount: predictions.length, + itemBuilder: (_, index) { + final prediction = predictions[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: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: prediction.description.toText14( + color: AppColors.textColor, + weight: FontWeight.w500), + ), + + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.blackColor, + width: 20.h, + height: 15.h, + fit: BoxFit.contain, + ), + ], + ).paddingAll(24.h),), + ), + ), + ).onPress(() async { + await context.read().selectPlacePrediction(prediction); + Navigator.of(context).pop(); + var location = context.read().placeDetails; + if(location != null) { + context.read().moveController( + Location(lat: location.lat, lng: location.lng)); + } + }); + + }, + ), + ); + }, + ) + ], + ); + } +} diff --git a/lib/presentation/emergency_services/widgets/nearestERItem.dart b/lib/presentation/emergency_services/widgets/nearestERItem.dart index 3dc2aa1..bbce56a 100644 --- a/lib/presentation/emergency_services/widgets/nearestERItem.dart +++ b/lib/presentation/emergency_services/widgets/nearestERItem.dart @@ -5,7 +5,7 @@ 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/model/resp_model/ProjectAvgERWaitingTime.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/ProjectAvgERWaitingTime.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'; diff --git a/lib/presentation/habib_wallet/widgets/hospital_list_item.dart b/lib/presentation/habib_wallet/widgets/hospital_list_item.dart index 8e10e28..ae47326 100644 --- a/lib/presentation/habib_wallet/widgets/hospital_list_item.dart +++ b/lib/presentation/habib_wallet/widgets/hospital_list_item.dart @@ -40,8 +40,8 @@ class HospitalListItemAdvancePayment extends StatelessWidget { child: Utils.buildSvgWithAssets( icon: AppAssets.forward_arrow_icon, iconColor: AppColors.blackColor, - width: 18, - height: 13, + width: 40.h, + height: 40.h, fit: BoxFit.contain, ), ), diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index fe8f025..3347902 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -16,6 +16,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/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_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/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; @@ -28,6 +29,8 @@ import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointmen import 'package:hmg_patient_app_new/presentation/authentication/quick_login.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; 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/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'; @@ -64,6 +67,7 @@ class _LandingPageState extends State { late InsuranceViewModel insuranceViewModel; late ImmediateLiveCareViewModel immediateLiveCareViewModel; late BookAppointmentsViewModel bookAppointmentsViewModel; + late EmergencyServicesViewModel emergencyServicesViewModel; final SwiperController _controller = SwiperController(); @@ -93,6 +97,7 @@ class _LandingPageState extends State { insuranceViewModel.initInsuranceProvider(); immediateLiveCareViewModel.initImmediateLiveCare(); immediateLiveCareViewModel.getPatientLiveCareHistory(); + emergencyServicesViewModel.checkPatientERAdvanceBalance(); } }); super.initState(); @@ -105,6 +110,7 @@ class _LandingPageState extends State { prescriptionsViewModel = Provider.of(context, listen: false); insuranceViewModel = Provider.of(context, listen: false); immediateLiveCareViewModel = Provider.of(context, listen: false); + emergencyServicesViewModel = Provider.of(context, listen: false); appState = getIt.get(); return PopScope( canPop: false, @@ -162,11 +168,12 @@ class _LandingPageState extends State { ); }), Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 18.h, width: 18.h).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: MedicalFilePage(), - // page: LoginScreen(), - ), + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.contactUs.tr(), + child: ContactUs(), + callBackFunc: () {}, + isFullScreen: false, ); }), ], @@ -263,15 +270,17 @@ class _LandingPageState extends State { ) : Container( width: double.infinity, - decoration: - RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), + decoration: RoundedRectangleBorder() + .toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), child: Padding( padding: EdgeInsets.all(12.h), child: Column( children: [ Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), SizedBox(height: 12.h), - "You do not have any upcoming appointment. Please book an appointment".needTranslation.toText12(isCenter: true), + "You do not have any upcoming appointment. Please book an appointment" + .needTranslation + .toText12(isCenter: true), SizedBox(height: 12.h), CustomButton( text: LocaleKeys.bookAppo.tr(context: context), @@ -286,7 +295,7 @@ class _LandingPageState extends State { padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), icon: AppAssets.add_icon, iconColor: AppColors.primaryRedColor, - height: (isFoldable || isTablet) ? 56.h : 46.h, + height: 46.h, ), ], ), @@ -294,6 +303,8 @@ class _LandingPageState extends State { ).paddingSymmetrical(24.h, 0.h); }, ), + + // Consumer for LiveCare pending request Consumer( builder: (context, immediateLiveCareVM, child) { return immediateLiveCareVM.patientHasPendingLiveCareRequest @@ -351,6 +362,66 @@ class _LandingPageState extends State { : SizedBox(height: 12.h); }, ), + + // Consumer for ER Online Check-In pending request + Consumer( + builder: (context, emergencyServicesVM, child) { + return emergencyServicesVM.patientHasAdvanceERBalance + ? Column( + children: [ + SizedBox(height: 4.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: true, + side: BorderSide(color: AppColors.primaryRedColor, width: 3.h), + ), + width: double.infinity, + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + AppCustomChipWidget( + labelText: "ER Online Check-In Request", + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.10), + textColor: AppColors.primaryRedColor, + ), + Utils.buildSvgWithAssets(icon: AppAssets.appointment_checkin_icon, width: 24.h, height: 24.h, iconColor: AppColors.primaryRedColor), + ], + ), + SizedBox(height: 8.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "You have ER Online Check-In Request".needTranslation.toText12(isBold: true), + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.blackColor, + width: 20.h, + height: 15.h, + fit: BoxFit.contain, + ), + ], + ), + ], + ), + ), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: ErOnlineCheckinHome())); + // context.read().navigateToEROnlineCheckIn(); + }), + SizedBox(height: 12.h), + ], + ) + : SizedBox(height: 12.h); + }, + ), + Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -459,7 +530,7 @@ class _LandingPageState extends State { ], ).paddingSymmetrical(24.h, 0.h), SizedBox( - height: 340.h, + height: 280.h, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: LandingPageData.getServiceCardsList.length, diff --git a/lib/presentation/home/navigation_screen.dart b/lib/presentation/home/navigation_screen.dart index c2bfae7..0599969 100644 --- a/lib/presentation/home/navigation_screen.dart +++ b/lib/presentation/home/navigation_screen.dart @@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointme import 'package:hmg_patient_app_new/presentation/hmg_services/services_page.dart'; import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; +import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart'; import 'package:hmg_patient_app_new/widgets/bottom_navigation/bottom_navigation.dart'; class LandingNavigation extends StatefulWidget { @@ -30,12 +31,11 @@ class _LandingNavigationState extends State { const LandingPage(), appState.isAuthenticated ? MedicalFilePage() : /* need add feedback page */ const LandingPage(), BookAppointmentPage(), - const LandingPage(), + const ToDoPage(), appState.isAuthenticated ? /* need add news page */ ServicesPage() : const LandingPage(), ], ), bottomNavigationBar: BottomNavigation( - context: context, currentIndex: _currentIndex, onTap: (index) { setState(() => _currentIndex = index); diff --git a/lib/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index ee5fcca..234fad1 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/emergency_services_page.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; @@ -10,6 +11,7 @@ import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.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 '../../../core/utils/utils.dart'; import '../../../theme/colors.dart'; @@ -106,9 +108,12 @@ class SmallServiceCard extends StatelessWidget { ); break; case "emergency": + context.read().flushData(); + context.read().getTransportationOrders(showLoader: false,); Navigator.of(context).push( CustomPageRoute( page: EmergencyServicesPage(), + settings: const RouteSettings(name: '/EmergencyServicesPage'), ), ); break; diff --git a/lib/presentation/medical_file/eye_measurement_details_page.dart b/lib/presentation/medical_file/eye_measurement_details_page.dart new file mode 100644 index 0000000..e69766a --- /dev/null +++ b/lib/presentation/medical_file/eye_measurement_details_page.dart @@ -0,0 +1,198 @@ +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/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/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/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/main.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:hmg_patient_app_new/widgets/custom_tab_bar.dart'; +import 'package:provider/provider.dart'; + +class EyeMeasurementDetailsPage extends StatelessWidget { + EyeMeasurementDetailsPage({super.key, required this.patientAppointmentHistoryResponseModel}); + + final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; + late BookAppointmentsViewModel bookAppointmentsViewModel; + + @override + Widget build(BuildContext context) { + bookAppointmentsViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: LocaleKeys.eyeMeasurements.tr(), + child: SingleChildScrollView( + child: Consumer(builder: (context, myAppointmentsVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + myAppointmentsViewModel: myAppointmentsVM, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: false, + isFromMedicalReport: true, + isForEyeMeasurements: true, + ), + ), + SizedBox(height: 16.h), + CustomTabBar( + activeTextColor: AppColors.primaryRedColor, + activeBackgroundColor: AppColors.primaryRedColor.withValues(alpha: .1), + tabs: [ + CustomTabBarModel(null, LocaleKeys.classes.tr()), + CustomTabBarModel(null, LocaleKeys.contactLens.tr()), + ], + onTabChange: (index) { + myAppointmentsVM.onEyeMeasurementsTabChanged(index); + }, + ), + SizedBox(height: 12.h), + getSelectedTabContent(myAppointmentsVM), + ], + ).paddingSymmetrical(24.w, 0); + }), + ), + ), + ); + } + + Widget getSelectedTabContent(MyAppointmentsViewModel myAppointmentsVM) { + switch (myAppointmentsVM.eyeMeasurementsTabSelectedIndex) { + case 0: + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r, hasShadow: true), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.rightEye.tr().toText14(isBold: true), + SizedBox(height: 16.h), + getRow(LocaleKeys.sphere.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].rightEyeSpherical}', '-'), + getRow(LocaleKeys.cylinder.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].rightEyeCylinder}', '-'), + getRow(LocaleKeys.axis.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].rightEyeAxis}', '-'), + getRow(LocaleKeys.prism.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].rightEyePrism}', '-'), + getRow(LocaleKeys.va.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].rightEyeVA}', '-'), + getRow(LocaleKeys.remarks.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].rightEyeRemarks}', '-', isLast: true), + ], + ), + ), + SizedBox(height: 16.h), + Container( + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r, hasShadow: true), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.leftEye.tr().needTranslation.toText14(isBold: true), + SizedBox(height: 16.h), + getRow(LocaleKeys.sphere.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].leftEyeSpherical}', '-'), + getRow(LocaleKeys.cylinder.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].leftEyeCylinder}', '-'), + getRow(LocaleKeys.axis.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].leftEyeAxis}', '-'), + getRow(LocaleKeys.prism.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].leftEyePrism}', '-'), + getRow(LocaleKeys.va.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].leftEyeVA}', '-'), + getRow(LocaleKeys.remarks.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].leftEyeRemarks}', '-', isLast: true), + ], + ), + ), + SizedBox(height: 24.h), + ], + ); + case 1: + return Column( + children: [ + Container( + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r, hasShadow: true), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.rightEye.tr().toText14(isBold: true), + SizedBox(height: 16.h), + getRow(LocaleKeys.brand.tr(), '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![0].brand}', ''), + getRow('B.C', '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![0].baseCurve}', ''), + getRow(LocaleKeys.power.tr(), '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![0].power}', ''), + getRow(LocaleKeys.diameter.tr(), '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![0].diameter}', ''), + getRow('OZ', '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![0].oZ}', ''), + getRow('CT', '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![0].cT}', ''), + getRow('Blend', '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![0].blend}', ''), + getRow(LocaleKeys.remarks.tr(), '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![0].remarks}', '', isLast: true), + ], + ), + ), + SizedBox(height: 16.h), + Container( + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r, hasShadow: true), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.leftEye.tr().needTranslation.toText14(isBold: true), + SizedBox(height: 16.h), + getRow(LocaleKeys.brand.tr(), '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].brand}', ''), + getRow('B.C', '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].baseCurve}', ''), + getRow(LocaleKeys.power.tr(), '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].power}', ''), + getRow(LocaleKeys.diameter.tr(), '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].diameter}', ''), + getRow('OZ', '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].oZ}', ''), + getRow('CT', '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].cT}', ''), + getRow('Blend', '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].blend}', ''), + getRow(LocaleKeys.remarks.tr(), '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].remarks}', '', isLast: true), + ], + ), + ), + SizedBox(height: 24.h), + ], + ); + default: + return Container(); + } + } + + Widget getRow(String title, String val1, String val2, {bool isLast = false}) => Padding( + padding: EdgeInsets.only(left: 8.w, right: 8.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.all(8.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(flex: 2, child: title.toText11(weight: FontWeight.w500)), + Expanded( + flex: 2, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + SizedBox(width: 120.w, child: (val1 == 'null' ? '-' : val1).toText10(isBold: true, textOverflow: TextOverflow.clip)), + (val2 == 'null' ? '-' : val2).toText10(isBold: true, textOverflow: TextOverflow.ellipsis), + ], + ), + ) + ], + ), + ), + isLast + ? Container( + height: 4, + ) + : Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 2.h) + ], + ), + ); +} diff --git a/lib/presentation/medical_file/eye_measurements_appointments_page.dart b/lib/presentation/medical_file/eye_measurements_appointments_page.dart new file mode 100644 index 0000000..3b82ad4 --- /dev/null +++ b/lib/presentation/medical_file/eye_measurements_appointments_page.dart @@ -0,0 +1,90 @@ +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/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/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/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 EyeMeasurementsAppointmentsPage extends StatelessWidget { + EyeMeasurementsAppointmentsPage({super.key}); + + late BookAppointmentsViewModel bookAppointmentsViewModel; + + @override + Widget build(BuildContext context) { + bookAppointmentsViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: "Eye Measurements", + child: SingleChildScrollView( + child: Consumer(builder: (context, myAppointmentsVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + ListView.separated( + scrollDirection: Axis.vertical, + itemCount: myAppointmentsVM.isEyeMeasurementsAppointmentsLoading + ? 5 + : myAppointmentsVM.patientEyeMeasurementsAppointmentsHistoryList.isNotEmpty + ? myAppointmentsVM.patientEyeMeasurementsAppointmentsHistoryList.length + : 1, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.only(left: 24.h, right: 24.h), + itemBuilder: (context, index) { + return myAppointmentsVM.isEyeMeasurementsAppointmentsLoading + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), + myAppointmentsViewModel: myAppointmentsVM, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: true, + isFromHomePage: false, + ), + ) + : myAppointmentsVM.patientEyeMeasurementsAppointmentsHistoryList.isNotEmpty + ? AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + 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: myAppointmentsVM.patientEyeMeasurementsAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsVM, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: false, + isForEyeMeasurements: true, + ), + ), + ), + ), + ) + : Utils.getNoDataWidget(context, noDataText: "No Ophthalmology appointments found...".needTranslation); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + SizedBox(height: 60.h), + ], + ); + }), + ), + ), + ); + } +} diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 07bd7f3..84efb9c 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -35,6 +35,7 @@ import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_upd import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/eye_measurements_appointments_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves_list_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/vaccine_list_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart'; @@ -136,7 +137,7 @@ class _MedicalFilePageState extends State { ).withHorizontalPadding(24.w).onPress(() { DialogService dialogService = getIt.get(); dialogService.showFamilyBottomSheetWithoutH( - label: "Who do you want to book for?".needTranslation, + label: "Family Files".needTranslation, message: "This clinic or doctor is only available for the below eligible profiles.".needTranslation, onSwitchPress: (FamilyFileResponseModelLists profile) { medicalFileViewModel.switchFamilyFiles(responseID: profile.responseId, patientID: profile.patientId, phoneNumber: profile.mobileNumber); @@ -144,133 +145,131 @@ class _MedicalFilePageState extends State { profiles: medicalFileViewModel.patientFamilyFiles); }), isLeading: false, - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 16.h), - TextInputWidget( - labelText: LocaleKeys.search.tr(context: context), - hintText: "Type any record".needTranslation, - controller: TextEditingController(), - keyboardType: TextInputType.number, - isEnable: true, - prefix: null, - autoFocus: false, - isBorderAllowed: false, - isAllowLeadingIcon: true, - padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), - leadingIcon: AppAssets.student_card, - ).paddingSymmetrical(24.w, 0.0), - SizedBox(height: 16.h), - Container( - width: double.infinity, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r), - child: Padding( - padding: EdgeInsets.all(16.w), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - 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, - children: [ - "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" - .toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1), - SizedBox(height: 4.h), - Wrap( - direction: Axis.horizontal, - spacing: 4.w, - runSpacing: 6.w, - children: [ - AppCustomChipWidget( - icon: AppAssets.file_icon, - labelText: "${LocaleKeys.fileNo.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}", - labelPadding: EdgeInsetsDirectional.only(end: 6.w), - onChipTap: () { - navigationService.pushPage( - page: FamilyMedicalScreen( - profiles: medicalFileViewModel.patientFamilyFiles, - onSelect: (FamilyFileResponseModelLists p1) {}, - )); - }, - ), - AppCustomChipWidget( - icon: AppAssets.checkmark_icon, - labelText: LocaleKeys.verified.tr(context: context), - iconColor: AppColors.successColor, - labelPadding: EdgeInsetsDirectional.only(end: 6.w), - ), - ], - ), - ], - ) - ], - ), - SizedBox(height: 16.h), - Divider(color: AppColors.dividerColor, height: 1.h), - SizedBox(height: 16.h), - Wrap( - direction: Axis.horizontal, - spacing: 4.h, - runSpacing: 4.h, - children: [ - AppCustomChipWidget( - labelText: "${appState.getAuthenticatedUser()!.age} Years Old", - labelPadding: EdgeInsetsDirectional.only(start: 8.w, end: 8.w), - ), - AppCustomChipWidget( - icon: AppAssets.blood_icon, - labelText: "Blood: ${appState.getUserBloodGroup.isEmpty ? "N/A" : appState.getUserBloodGroup.isEmpty}", - iconColor: AppColors.primaryRedColor, - labelPadding: EdgeInsetsDirectional.only(end: 8.w), - ), - Consumer(builder: (context, insuranceVM, child) { - return AppCustomChipWidget( - icon: insuranceVM.isInsuranceExpired ? AppAssets.cancel_circle_icon : AppAssets.insurance_active_icon, - labelText: insuranceVM.isInsuranceExpired ? "Insurance Expired".needTranslation : "Insurance Active".needTranslation, - iconColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, - textColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, - iconSize: 12.w, - backgroundColor: - insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1), - labelPadding: EdgeInsetsDirectional.only(end: 8.w), - ); - }), - ], - ), - ], - ), - ), - ).paddingSymmetrical(24.w, 0.0), - SizedBox(height: 16.h), - Consumer(builder: (context, medicalFileVM, child) { - return Column( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + TextInputWidget( + labelText: LocaleKeys.search.tr(context: context), + hintText: "Type any record".needTranslation, + controller: TextEditingController(), + keyboardType: TextInputType.number, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + isAllowLeadingIcon: true, + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), + leadingIcon: AppAssets.student_card, + ).paddingSymmetrical(24.w, 0.0), + SizedBox(height: 16.h), + Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r), + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - CustomTabBar( - activeTextColor: AppColors.primaryRedColor, - activeBackgroundColor: AppColors.primaryRedColor.withValues(alpha: .1), - tabs: [ - CustomTabBarModel(AppAssets.myFilesBottom, LocaleKeys.general.tr(context: context).needTranslation), - CustomTabBarModel(AppAssets.insurance, LocaleKeys.insurance.tr(context: context)), - CustomTabBarModel(AppAssets.requests, LocaleKeys.request.tr(context: context).needTranslation), - CustomTabBarModel(AppAssets.more, "More".needTranslation), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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, + children: [ + "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" + .toText18(isBold: true, weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1), + SizedBox(height: 4.h), + Wrap( + direction: Axis.horizontal, + spacing: 4.w, + runSpacing: 6.w, + children: [ + AppCustomChipWidget( + icon: AppAssets.file_icon, + labelText: "${LocaleKeys.fileNo.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}", + labelPadding: EdgeInsetsDirectional.only(end: 6.w), + onChipTap: () { + navigationService.pushPage( + page: FamilyMedicalScreen( + profiles: medicalFileViewModel.patientFamilyFiles, + onSelect: (FamilyFileResponseModelLists p1) {}, + )); + }, + ), + AppCustomChipWidget( + icon: AppAssets.checkmark_icon, + labelText: LocaleKeys.verified.tr(context: context), + iconColor: AppColors.successColor, + labelPadding: EdgeInsetsDirectional.only(end: 6.w), + ), + ], + ), + ], + ) ], - onTabChange: (index) { - medicalFileVM.onTabChanged(index); - }, - ).paddingSymmetrical(24.w, 0.0), - SizedBox(height: 24.h), - getSelectedTabData(medicalFileVM.selectedTabIndex), + ), + SizedBox(height: 16.h), + Divider(color: AppColors.dividerColor, height: 1.h), + SizedBox(height: 16.h), + Wrap( + direction: Axis.horizontal, + spacing: 4.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + labelText: "${appState.getAuthenticatedUser()!.age} Years Old", + labelPadding: EdgeInsetsDirectional.only(start: 8.w, end: 8.w), + ), + AppCustomChipWidget( + icon: AppAssets.blood_icon, + labelText: "Blood: ${appState.getUserBloodGroup.isEmpty ? "N/A" : appState.getUserBloodGroup.isEmpty}", + iconColor: AppColors.primaryRedColor, + labelPadding: EdgeInsetsDirectional.only(end: 8.w), + ), + Consumer(builder: (context, insuranceVM, child) { + return AppCustomChipWidget( + icon: insuranceVM.isInsuranceExpired ? AppAssets.cancel_circle_icon : AppAssets.insurance_active_icon, + labelText: insuranceVM.isInsuranceExpired ? "Insurance Expired".needTranslation : "Insurance Active".needTranslation, + iconColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, + textColor: insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor, + iconSize: 12.w, + backgroundColor: + insuranceVM.isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1), + labelPadding: EdgeInsetsDirectional.only(end: 8.w), + ); + }), + ], + ), ], - ); - }), - ], - ), + ), + ), + ).paddingSymmetrical(24.w, 0.0), + SizedBox(height: 16.h), + Consumer(builder: (context, medicalFileVM, child) { + return Column( + children: [ + CustomTabBar( + activeTextColor: AppColors.primaryRedColor, + activeBackgroundColor: AppColors.primaryRedColor.withValues(alpha: .1), + tabs: [ + CustomTabBarModel(AppAssets.myFilesBottom, LocaleKeys.general.tr(context: context).needTranslation), + CustomTabBarModel(AppAssets.insurance, LocaleKeys.insurance.tr(context: context)), + CustomTabBarModel(AppAssets.requests, LocaleKeys.request.tr(context: context).needTranslation), + CustomTabBarModel(AppAssets.more, "More".needTranslation), + ], + onTabChange: (index) { + medicalFileVM.onTabChanged(index); + }, + ).paddingSymmetrical(24.w, 0.0), + SizedBox(height: 24.h), + getSelectedTabData(medicalFileVM.selectedTabIndex), + ], + ); + }), + ], ), ); } @@ -343,7 +342,9 @@ class _MedicalFilePageState extends State { }), SizedBox(height: 16.h), Consumer(builder: (context, myAppointmentsVM, child) { + // Provide an explicit height so the horizontal ListView has a bounded height return SizedBox( + height: 190.h, child: myAppointmentsVM.isMyAppointmentsLoading ? MedicalFileAppointmentCard( patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), @@ -355,7 +356,8 @@ class _MedicalFilePageState extends State { ? Container( padding: EdgeInsets.all(12.w), width: MediaQuery.of(context).size.width, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: true), + decoration: + RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r, hasShadow: true), child: Column( children: [ Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h), @@ -377,8 +379,8 @@ class _MedicalFilePageState extends State { fontSize: 14.f, fontWeight: FontWeight.w500, borderRadius: 12.r, - padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), - height: isFoldable ? 50.h : 40.h, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: isFoldable || isTablet ? 50.h : 40.h, icon: AppAssets.add_icon, iconColor: AppColors.primaryRedColor, ), @@ -501,11 +503,13 @@ class _MedicalFilePageState extends State { spacing: 3.w, runSpacing: 4.w, children: [ - AppCustomChipWidget(labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!), + AppCustomChipWidget( + labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!), AppCustomChipWidget( icon: AppAssets.doctor_calendar_icon, labelText: DateUtil.formatDateToDate( - DateUtil.convertStringToDate(prescriptionVM.patientPrescriptionOrders[index].appointmentDate), + DateUtil.convertStringToDate( + prescriptionVM.patientPrescriptionOrders[index].appointmentDate), false, ), ), @@ -528,7 +532,8 @@ class _MedicalFilePageState extends State { prescriptionVM.setPrescriptionsDetailsLoading(); Navigator.of(context).push( CustomPageRoute( - page: PrescriptionDetailPage(prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]), + page: PrescriptionDetailPage( + prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]), ), ); }), @@ -638,7 +643,10 @@ class _MedicalFilePageState extends State { fit: BoxFit.cover, ).circle(100).toShimmer2(isShow: true, radius: 50.r), SizedBox(height: 8.h), - ("Dr. John Smith Smith Smith").toString().toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2).toShimmer2(isShow: true), + ("Dr. John Smith Smith Smith") + .toString() + .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) + .toShimmer2(isShow: true), ], ) : myAppointmentsVM.patientMyDoctorsList.isEmpty @@ -658,7 +666,7 @@ class _MedicalFilePageState extends State { ), ).paddingSymmetrical(24.w, 0.h) : SizedBox( - height: 200.h, + height: 100.h, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: myAppointmentsVM.patientMyDoctorsList.length, @@ -721,7 +729,7 @@ class _MedicalFilePageState extends State { }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h), ), - ); + ).paddingSymmetrical(24.w, 0); }), SizedBox(height: 24.h), "Others".needTranslation.toText18(isBold: true).paddingSymmetrical(24.w, 0.h), @@ -731,7 +739,7 @@ class _MedicalFilePageState extends State { crossAxisCount: 3, crossAxisSpacing: 16.h, mainAxisSpacing: 16.w, - mainAxisExtent: 130.h, + mainAxisExtent: 115.h, ), physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, @@ -744,7 +752,16 @@ class _MedicalFilePageState extends State { svgIcon: AppAssets.eye_result_icon, isLargeText: true, iconSize: 36.w, - ), + ).onPress(() { + myAppointmentsViewModel.setIsEyeMeasurementsAppointmentsLoading(true); + myAppointmentsViewModel.onEyeMeasurementsTabChanged(0); + myAppointmentsViewModel.getPatientEyeMeasurementAppointments(); + Navigator.of(context).push( + CustomPageRoute( + page: EyeMeasurementsAppointmentsPage(), + ), + ); + }), MedicalFileCard( label: "Allergy Info".needTranslation, textColor: AppColors.blackColor, @@ -810,10 +827,14 @@ class _MedicalFilePageState extends State { text: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.updateInsuranceSubtitle.tr(context: context)}", onPressed: () { insuranceViewModel.setIsInsuranceUpdateDetailsLoading(true); - insuranceViewModel.getPatientInsuranceDetailsForUpdate( - appState.getAuthenticatedUser()!.patientId.toString(), appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); + insuranceViewModel.getPatientInsuranceDetailsForUpdate(appState.getAuthenticatedUser()!.patientId.toString(), + appState.getAuthenticatedUser()!.patientIdentificationNo.toString()); showCommonBottomSheetWithoutHeight(context, - child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false); + child: PatientInsuranceCardUpdateCard(), + callBackFunc: () {}, + title: "", + isCloseButtonVisible: false, + isFullScreen: false); }, backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), borderColor: AppColors.bgGreenColor.withOpacity(0.0), @@ -833,7 +854,7 @@ class _MedicalFilePageState extends State { crossAxisCount: 3, crossAxisSpacing: 16.h, mainAxisSpacing: 16.w, - mainAxisExtent: 150.h, + mainAxisExtent: 120.h, ), physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.only(top: 12.h), @@ -920,7 +941,7 @@ class _MedicalFilePageState extends State { crossAxisCount: 3, crossAxisSpacing: 16.h, mainAxisSpacing: 16.w, - mainAxisExtent: 140.h, + mainAxisExtent: 110.h, ), physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, diff --git a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart index 0f382dc..3924761 100644 --- a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart @@ -88,7 +88,7 @@ class MedicalFileAppointmentCard extends StatelessWidget { myAppointmentsViewModel.isMyAppointmentsLoading ? Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r) : Expanded( - flex: 6, + flex: 7, child: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? getArrivedAppointmentButton(context).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading) : CustomButton( @@ -105,7 +105,8 @@ class MedicalFileAppointmentCard extends StatelessWidget { }, backgroundColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15), - borderColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + borderColor: + AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), fontSize: 14.f, fontWeight: FontWeight.w500, diff --git a/lib/presentation/medical_file/widgets/medical_file_card.dart b/lib/presentation/medical_file/widgets/medical_file_card.dart index 00d62c9..b96026b 100644 --- a/lib/presentation/medical_file/widgets/medical_file_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_card.dart @@ -30,17 +30,16 @@ class MedicalFileCard extends StatelessWidget { color: backgroundColor, borderRadius: 12.r, ), - child: Padding( - padding: EdgeInsets.all(12.w), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Utils.buildSvgWithAssets(icon: svgIcon, width: iconS, height: iconS, fit: BoxFit.contain), - SizedBox(height: 12.h), - isLargeText ? label.toText13(color: textColor, isBold: true, maxLine: 2) : label.toText11(color: textColor, isBold: true, maxLine: 2), - ], - ), + padding: EdgeInsets.all(12.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Utils.buildSvgWithAssets(icon: svgIcon, width: iconS, height: iconS, fit: BoxFit.contain), + SizedBox(height: 8.h), + isLargeText ? label.toText13(color: textColor, isBold: true, maxLine: 2) : label.toText11(color: textColor, isBold: true, maxLine: 2), + ], ), ); } diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart index 9b1fb4b..4db5470 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -54,7 +54,10 @@ class _FamilyCardsState extends State { children: [ Utils.buildSvgWithAssets(icon: AppAssets.alertSquare), SizedBox(width: 8.h), - "Who can view my medical file ?".needTranslation.toText14(color: AppColors.textColor, isUnderLine: true, weight: FontWeight.w500).onPress(() { + "Who can view my medical file ?" + .needTranslation + .toText14(color: AppColors.textColor, isUnderLine: true, weight: FontWeight.w500) + .onPress(() { dialogService.showFamilyBottomSheetWithoutHWithChild( label: "Manage Family".needTranslation, message: "", @@ -110,11 +113,18 @@ class _FamilyCardsState extends State { ? AppColors.textGreenColor : AppColors.alertColor), SizedBox(height: 8.h), - Wrap(alignment: WrapAlignment.start, crossAxisAlignment: WrapCrossAlignment.start, runAlignment: WrapAlignment.start, spacing: 0.h, children: [ - (profile.patientName ?? "").toText14(isBold: false, isCenter: false, maxlines: 1, weight: FontWeight.w600), - (getStatusTextByRequest(FamilyFileEnum.values.firstWhere((e) => e.toInt == profile.status), profile.isRequestFromMySide ?? false)) - .toText12(isBold: false, isCenter: false, maxLine: 1, fontWeight: FontWeight.w500, color: AppColors.greyTextColor), - ]), + Wrap( + alignment: WrapAlignment.start, + crossAxisAlignment: WrapCrossAlignment.start, + runAlignment: WrapAlignment.start, + spacing: 0.h, + children: [ + (profile.patientName ?? "").toText14(isBold: false, isCenter: false, maxlines: 1, weight: FontWeight.w600), + (getStatusTextByRequest( + FamilyFileEnum.values.firstWhere((e) => e.toInt == profile.status), profile.isRequestFromMySide ?? false)) + .toText12( + isBold: false, isCenter: false, maxLine: 1, fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + ]), SizedBox(height: 8.h), CustomChipWidget( height: 30.h, @@ -141,7 +151,7 @@ class _FamilyCardsState extends State { itemCount: widget.profiles.length, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, - crossAxisSpacing: 10.h, + crossAxisSpacing: 10.w, mainAxisSpacing: 10.h, childAspectRatio: widget.isShowDetails ? 0.56.h : 0.65.h, ), @@ -162,20 +172,21 @@ class _FamilyCardsState extends State { mainAxisSize: MainAxisSize.min, children: [ Utils.buildImgWithAssets( - icon: profile.gender == null - ? AppAssets.dummy_user - : profile.gender == 1 - ? ((profile.age ?? 0) < 7 ? AppAssets.babyBoyImg : AppAssets.male_img) - : (profile.age! < 7 ? AppAssets.babyGirlImg : AppAssets.femaleImg), - width: 80.h, - height: 78.h), + icon: profile.gender == null + ? AppAssets.dummy_user + : profile.gender == 1 + ? ((profile.age ?? 0) < 7 ? AppAssets.babyBoyImg : AppAssets.male_img) + : (profile.age! < 7 ? AppAssets.babyGirlImg : AppAssets.femaleImg), + width: 72.h, + height: 70.h, + ), SizedBox(height: 8.h), (profile.patientName ?? "Unknown").toText14(isBold: false, isCenter: true, maxlines: 1, weight: FontWeight.w600), SizedBox(height: 8.h), CustomChipWidget( chipType: ChipTypeEnum.alert, backgroundColor: AppColors.lightGrayBGColor, - chipText: "Relation:${profile.relationship ?? "N/A"}", + chipText: "Relation:${profile.relationship ?? " N/A"}", iconAsset: AppAssets.heart, isShowBorder: false, borderRadius: 8.h, @@ -282,7 +293,9 @@ class _FamilyCardsState extends State { alignment: WrapAlignment.start, children: [ (profile.patientName ?? "").toText14(isBold: false, isCenter: true, maxlines: 1, weight: FontWeight.w600), - (getStatusTextByRequest(FamilyFileEnum.values.firstWhere((e) => e.toInt == profile.status), profile.isRequestFromMySide ?? false)).toText14( + (getStatusTextByRequest( + FamilyFileEnum.values.firstWhere((e) => e.toInt == profile.status), profile.isRequestFromMySide ?? false)) + .toText14( isBold: false, isCenter: true, maxlines: 1, diff --git a/lib/presentation/onboarding/onboarding_screen.dart b/lib/presentation/onboarding/onboarding_screen.dart index c1e714e..265559b 100644 --- a/lib/presentation/onboarding/onboarding_screen.dart +++ b/lib/presentation/onboarding/onboarding_screen.dart @@ -81,15 +81,15 @@ class _OnboardingScreenState extends State { duration: const Duration(milliseconds: 250), height: 6.h, width: selectedIndex == 0 ? 18.w : 6.w, - decoration: - BoxDecoration(color: selectedIndex == 0 ? AppColors.textColor : AppColors.inputLabelTextColor, borderRadius: BorderRadius.circular(30.r)), + decoration: BoxDecoration( + color: selectedIndex == 0 ? AppColors.textColor : AppColors.inputLabelTextColor, borderRadius: BorderRadius.circular(30.r)), ), AnimatedContainer( duration: const Duration(milliseconds: 250), height: 6.h, width: selectedIndex == 1 ? 18.w : 6.w, - decoration: - BoxDecoration(color: selectedIndex == 1 ? AppColors.textColor : AppColors.inputLabelTextColor, borderRadius: BorderRadius.circular(30.r)), + decoration: BoxDecoration( + color: selectedIndex == 1 ? AppColors.textColor : AppColors.inputLabelTextColor, borderRadius: BorderRadius.circular(30.r)), ), ], ).paddingOnly(left: 24.w, right: 24.w), @@ -146,7 +146,7 @@ class _OnboardingScreenState extends State { ), ), ], - ).paddingOnly(bottom: 10.h), + ).paddingOnly(bottom: (isFoldable || isTablet) ? 20.h : 10.h), ], ), ), diff --git a/lib/presentation/onboarding/splash_animation_screen.dart b/lib/presentation/onboarding/splash_animation_screen.dart index bca9681..7acd078 100644 --- a/lib/presentation/onboarding/splash_animation_screen.dart +++ b/lib/presentation/onboarding/splash_animation_screen.dart @@ -59,7 +59,7 @@ class _SplashAnimationScreenState extends State with Sing } } -// todo: do-not remove this code,as animation need to test on multiple screen sizes +// todo_section: do-not remove this code,as animation need to test on multiple screen sizes class AnimatedScreen extends StatefulWidget { const AnimatedScreen({super.key}); diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index 473f79a..e0e78c2 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -13,15 +13,13 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/prescriptions/models/resp_models/patient_prescriptions_response_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_item_view.dart'; -import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_reminder_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; -import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; import 'package:open_filex/open_filex.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -127,7 +125,8 @@ class _PrescriptionDetailPageState extends State { children: [ AppCustomChipWidget( icon: AppAssets.doctor_calendar_icon, - labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), false), + labelText: DateUtil.formatDateToDate( + DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), false), labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h), ), AppCustomChipWidget( @@ -214,18 +213,22 @@ class _PrescriptionDetailPageState extends State { hasShadow: true, ), child: CustomButton( - text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context), + text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! + ? LocaleKeys.resendOrder.tr(context: context) + : LocaleKeys.prescriptionDeliveryError.tr(context: context), onPressed: () {}, backgroundColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.greyF7Color, borderColor: AppColors.successColor.withOpacity(0.01), - textColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.whiteColor : AppColors.textColor.withOpacity(0.35), + textColor: + widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.whiteColor : AppColors.textColor.withOpacity(0.35), fontSize: 16, fontWeight: FontWeight.w500, borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), height: 50.h, icon: AppAssets.prescription_refill_icon, - iconColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.whiteColor : AppColors.textColor.withOpacity(0.35), + iconColor: + widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.whiteColor : AppColors.textColor.withOpacity(0.35), iconSize: 20.h, ).paddingSymmetrical(24.h, 24.h), ), diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index 34e6988..ee7465d 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -184,10 +184,7 @@ class _ProfileSettingsState extends State { ), ], ), - "Quick Actions" - .needTranslation - .toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1) - .paddingOnly(left: 24.w, right: 24.w), + "Quick Actions".needTranslation.toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1).paddingOnly(left: 24.w, right: 24.w), Container( margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 24.h), padding: EdgeInsets.only(top: 4.h, bottom: 4.h), @@ -195,8 +192,7 @@ class _ProfileSettingsState extends State { child: Column( children: [ actionItem(AppAssets.language_change, "Language".needTranslation, () { - showCommonBottomSheetWithoutHeight(context, - title: "Application Language".needTranslation, child: AppLanguageChange(), callBackFunc: () {}, isFullScreen: false); + showCommonBottomSheetWithoutHeight(context, title: "Application Language".needTranslation, child: AppLanguageChange(), callBackFunc: () {}, isFullScreen: false); }, trailingLabel: Utils.appState.isArabic() ? "العربية".needTranslation : "English".needTranslation), 1.divider, actionItem(AppAssets.accessibility, "Accessibility".needTranslation, () {}), @@ -207,10 +203,7 @@ class _ProfileSettingsState extends State { ], ), ), - "Personal Information" - .needTranslation - .toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1) - .paddingOnly(left: 24.w, right: 24.w), + "Personal Information".needTranslation.toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1).paddingOnly(left: 24.w, right: 24.w), Container( margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 24.h), padding: EdgeInsets.only(top: 4.h, bottom: 4.h), @@ -227,10 +220,7 @@ class _ProfileSettingsState extends State { ], ), ), - "Help & Support" - .needTranslation - .toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1) - .paddingOnly(left: 24.w, right: 24.w), + "Help & Support".needTranslation.toText18(weight: FontWeight.w600, textOverflow: TextOverflow.ellipsis, maxlines: 1).paddingOnly(left: 24.w, right: 24.w), Container( margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h), padding: EdgeInsets.only(top: 4.h, bottom: 4.h), @@ -271,13 +261,12 @@ class _ProfileSettingsState extends State { children: [ Utils.buildSvgWithAssets(icon: icon, iconColor: AppColors.greyTextColor), label.toText14(weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, - if (trailingLabel.isNotEmpty) - trailingLabel.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1), + if (trailingLabel.isNotEmpty) trailingLabel.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500, textOverflow: TextOverflow.ellipsis, maxlines: 1), switchValue != null ? Switch( value: switchValue, onChanged: (value) {}, - // activeThumbColor: AppColors.successColor, + activeColor: AppColors.successColor, activeTrackColor: AppColors.successColor.withValues(alpha: .15), ) : Transform.scale( diff --git a/lib/presentation/radiology/radiology_orders_page.dart b/lib/presentation/radiology/radiology_orders_page.dart index 39138e1..1d7dfe2 100644 --- a/lib/presentation/radiology/radiology_orders_page.dart +++ b/lib/presentation/radiology/radiology_orders_page.dart @@ -11,17 +11,16 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/radiology/search_radiology.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/radiology/radiology_result_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/chip/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'; @@ -41,7 +40,7 @@ class _RadiologyOrdersPageState extends State { @override void initState() { scheduleMicrotask(() { - radiologyViewModel.initRadiologyProvider(); + radiologyViewModel.initRadiologyViewModel(); }); super.initState(); } @@ -117,127 +116,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( + 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: [ - 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), + labelText: LocaleKeys.resultsAvailable.tr(context: context), + backgroundColor: AppColors.successColor.withOpacity(0.15), + textColor: AppColors.successColor, + ).toShimmer2(isShow: model.isRadiologyOrdersLoading, width: 100), + SizedBox(height: 8.h), + Row( + children: [ + Image.network( + model.isRadiologyOrdersLoading + ? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png" + : model.patientRadiologyOrders[index].doctorImageURL!, + width: 24.h, + height: 24.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: model.isRadiologyOrdersLoading), + SizedBox(width: 4.h), + (model.isRadiologyOrdersLoading + ? "Dr John Smith" + : model.patientRadiologyOrders[index].doctorName!) + .toText16(isBold: true) + .toShimmer2(isShow: model.isRadiologyOrdersLoading) + ], + ), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: model.isRadiologyOrdersLoading + ? "01 Jan 2025" + : DateUtil.formatDateToDate(model.patientRadiologyOrders[index].orderDate!, false), + ).toShimmer2(isShow: model.isRadiologyOrdersLoading), + AppCustomChipWidget( + labelText: model.isRadiologyOrdersLoading + ? "01 Jan 2025" + : model.patientRadiologyOrders[index].clinicDescription!, + ).toShimmer2(isShow: model.isRadiologyOrdersLoading), - // AppCustomChipWidget(labelText: "").toShimmer2(isShow: model.isRadiologyOrdersLoading, width: 16.h), - // AppCustomChipWidget(labelText: "").toShimmer2(isShow: model.isRadiologyOrdersLoading, width: 16.h), + // AppCustomChipWidget(labelText: "").toShimmer2(isShow: model.isRadiologyOrdersLoading, width: 16.h), + // AppCustomChipWidget(labelText: "").toShimmer2(isShow: model.isRadiologyOrdersLoading, width: 16.h), + ], + ), ], ), - ], - ), - ), - model.isRadiologyOrdersLoading - ? SizedBox.shrink() - : AnimatedCrossFade( - firstChild: SizedBox.shrink(), - secondChild: Padding( - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.only(bottom: 8.h), - child: '● ${model.patientRadiologyOrders[index].description}'.toText14(weight: FontWeight.w500), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + ), + model.isRadiologyOrdersLoading + ? SizedBox.shrink() + : AnimatedCrossFade( + firstChild: SizedBox.shrink(), + secondChild: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox(), - CustomButton( - icon: AppAssets.view_report_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - text: LocaleKeys.viewReport.tr(context: context), - onPressed: () { - Navigator.of(context).push( - CustomPageRoute( - page: RadiologyResultPage(patientRadiologyResponseModel: model.patientRadiologyOrders[index]), - ), - ); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14, - fontWeight: FontWeight.bold, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, + Padding( + padding: EdgeInsets.only(bottom: 8.h), + child: '● ${model.patientRadiologyOrders[index].description}' + .toText14(weight: FontWeight.w500), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(), + CustomButton( + icon: AppAssets.view_report_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + text: LocaleKeys.viewReport.tr(context: context), + onPressed: () { + Navigator.of(context).push( + CustomPageRoute( + page: RadiologyResultPage( + patientRadiologyResponseModel: model.patientRadiologyOrders[index]), + ), + ); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.bold, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], ), ], ), - ], + ), + crossFadeState: isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst, + duration: Duration(milliseconds: 300), ), - ), - crossFadeState: isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst, - duration: Duration(milliseconds: 300), - ), - ], + ], + ), + ), ), ), ), - ), - ), - ) - : Utils.getNoDataWidget(context, noDataText: "You don't have any radiology results yet.".needTranslation); + ) + : Utils.getNoDataWidget(context, noDataText: "You don't have any radiology results yet.".needTranslation); }, ), ], diff --git a/lib/presentation/todo/todo_page.dart b/lib/presentation/todo/todo_page.dart deleted file mode 100644 index 65b762f..0000000 --- a/lib/presentation/todo/todo_page.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/theme/colors.dart'; - -class ToDoPage extends StatelessWidget { - const ToDoPage({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_section/ancillary_order_payment_page.dart b/lib/presentation/todo_section/ancillary_order_payment_page.dart new file mode 100644 index 0000000..f9995dc --- /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 0000000..449d21e --- /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 0000000..f2eb50c --- /dev/null +++ b/lib/presentation/todo_section/todo_page.dart @@ -0,0 +1,88 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/todo_section/ancillary_procedures_details_page.dart'; +import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:provider/provider.dart'; + +class ToDoPage extends StatefulWidget { + const ToDoPage({super.key}); + + @override + State createState() => _ToDoPageState(); +} + +class _ToDoPageState extends State { + @override + void initState() { + final TodoSectionViewModel todoSectionViewModel = context.read(); + scheduleMicrotask(() async { + await todoSectionViewModel.initializeTodoSectionViewModel(); + }); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + Widget _buildLoadingShimmer() { + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: 3, + itemBuilder: (context, index) { + return AncillaryOrderCard( + order: AncillaryOrderItem(), + isLoading: true, + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: "ToDo List".needTranslation, + isLeading: false, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + "Ancillary Orders".needTranslation.toText18(isBold: true), + Consumer( + builder: (BuildContext context, TodoSectionViewModel todoSectionViewModel, Widget? child) { + return todoSectionViewModel.isAncillaryOrdersLoading + ? _buildLoadingShimmer() + : AncillaryOrdersList( + orders: todoSectionViewModel.patientAncillaryOrdersList, + onCheckIn: (order) => log("Check-in for order: ${order.orderNo}"), + onViewDetails: (order) async { + Navigator.of(context).push(CustomPageRoute( + page: 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 0000000..31a778f --- /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 0000000..ba2f94d --- /dev/null +++ b/lib/presentation/todo_section/widgets/ancillary_procedures_list.dart @@ -0,0 +1,274 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_list_response_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; + +class AncillaryProceduresList extends StatelessWidget { + final List orders; + final Function(AncillaryOrderItem order)? onCheckIn; + final Function(AncillaryOrderItem order)? onViewDetails; + + const AncillaryProceduresList({ + super.key, + required this.orders, + this.onCheckIn, + this.onViewDetails, + }); + + @override + Widget build(BuildContext context) { + // Show empty state + if (orders.isEmpty) { + return _buildDefaultEmptyState(context); + } + + // Show orders list + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: orders.length, + itemBuilder: (context, index) { + final order = orders[index]; + + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: AncillaryOrderCard( + order: order, + isLoading: false, + onCheckIn: onCheckIn != null ? () => onCheckIn!(order) : null, + onViewDetails: onViewDetails != null ? () => onViewDetails!(order) : null, + )), + ), + ), + ); + }, + ); + } + + Widget _buildDefaultEmptyState(BuildContext context) { + return Center( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 40.h), + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + hasShadow: false, + ), + child: Utils.getNoDataWidget( + context, + noDataText: "You don't have any ancillary orders yet.".needTranslation, + isSmallWidget: true, + width: 62.w, + height: 62.h, + ), + ), + ), + ); + } +} + +class AncillaryOrderCard extends StatelessWidget { + const AncillaryOrderCard({ + super.key, + required this.order, + this.isLoading = false, + this.onCheckIn, + this.onViewDetails, + }); + + final AncillaryOrderItem order; + final bool isLoading; + final VoidCallback? onCheckIn; + final VoidCallback? onViewDetails; + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only(bottom: 12.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header Row with Order Number and Date + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + "Order #".needTranslation.toText14( + color: AppColors.textColorLight, + weight: FontWeight.w500, + ), + SizedBox(width: 4.w), + (isLoading ? "12345" : "${order.orderNo ?? '-'}").toText16(isBold: true).toShimmer2(isShow: isLoading), + ], + ), + if (order.orderDate != null || isLoading) + (isLoading ? "Jan 15, 2024" : DateFormat('MMM dd, yyyy').format(order.orderDate!)) + .toText12(color: AppColors.textColorLight) + .toShimmer2(isShow: isLoading), + ], + ), + + SizedBox(height: 12.h), + + // Doctor and Clinic Info + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Doctor Name + if (order.doctorName != null || isLoading) + (isLoading ? "Dr. John Smith" : order.doctorName!) + .toString() + .toText14(isBold: true, maxlines: 2) + .toShimmer2(isShow: isLoading), + + SizedBox(height: 4.h), + + // Clinic Name + if (order.clinicName != null || isLoading) + (isLoading ? "Cardiology Clinic" : order.clinicName!) + .toString() + .toText12( + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + maxLine: 2, + ) + .toShimmer2(isShow: isLoading), + ], + ), + ), + ], + ), + + SizedBox(height: 12.h), + + // Chips for Appointment Info and Status + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + // Appointment Date + if (order.appointmentDate != null || isLoading) + AppCustomChipWidget( + icon: AppAssets.calendar, + labelText: + isLoading ? "Date: Jan 20, 2024" : "Date: ${DateFormat('MMM dd, yyyy').format(order.appointmentDate!)}".needTranslation, + ).toShimmer2(isShow: isLoading), + + // Appointment Number + if (order.appointmentNo != null || isLoading) + AppCustomChipWidget( + labelText: isLoading ? "Appt #: 98765" : "Appt #: ${order.appointmentNo}".needTranslation, + ).toShimmer2(isShow: isLoading), + + // Invoice Number + if (order.invoiceNo != null || isLoading) + AppCustomChipWidget( + labelText: isLoading ? "Invoice: 45678" : "Invoice: ${order.invoiceNo}".needTranslation, + ).toShimmer2(isShow: isLoading), + + // Queued Status + if (order.isQueued == true || isLoading) + AppCustomChipWidget( + labelText: "Queued".needTranslation, + ).toShimmer2(isShow: isLoading), + + // Check-in Available Status + if (order.isCheckInAllow == true || isLoading) + AppCustomChipWidget( + labelText: "Check-in Ready".needTranslation, + ).toShimmer2(isShow: isLoading), + ], + ), + + SizedBox(height: 12.h), + + // Action Buttons + Row( + children: [ + // Check-in Button (if available) + if (order.isCheckInAllow == true || isLoading) + Expanded( + child: CustomButton( + text: "Check In".needTranslation, + onPressed: () { + if (isLoading) { + return; + } else if (onCheckIn != null) { + onCheckIn!(); + } + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 10.r, + padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), + height: 40.h, + ).toShimmer2(isShow: isLoading), + ), + + if (order.isCheckInAllow == true || isLoading) SizedBox(width: 8.w), + + // View Details Button + Expanded( + child: CustomButton( + text: "View Details".needTranslation, + onPressed: () { + if (isLoading) { + return; + } else if (onViewDetails != null) { + onViewDetails!(); + } + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Color(0xffED1C2B), + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 10.r, + padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), + height: 40.h, + icon: AppAssets.arrow_forward, + iconColor: AppColors.primaryRedColor, + iconSize: 15.h, + ).toShimmer2(isShow: isLoading), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/services/analytics/flows/app_nav.dart b/lib/services/analytics/flows/app_nav.dart index bd9186c..75570c6 100644 --- a/lib/services/analytics/flows/app_nav.dart +++ b/lib/services/analytics/flows/app_nav.dart @@ -18,7 +18,7 @@ class AppNav{ if(tabIndex == 3) nav_name = "my family"; if(tabIndex == 4) - nav_name = "todo list"; + nav_name = "todo_section list"; if(tabIndex == 5) nav_name = "help"; diff --git a/lib/services/analytics/flows/todo_list.dart b/lib/services/analytics/flows/todo_list.dart index c1c874b..1cc7644 100644 --- a/lib/services/analytics/flows/todo_list.dart +++ b/lib/services/analytics/flows/todo_list.dart @@ -68,7 +68,7 @@ class TodoList{ // to_do_list_confirm_appointment(AppoitmentAllHistoryResultList appointment){ // logger('confirm_appointment', parameters: { // 'appointment_type' : appointment.isLiveCareAppointment! ? 'livecare' : 'regular', - // 'flow_type' : 'todo list', + // 'flow_type' : 'todo_section list', // 'clinic_type_online' : appointment.clinicName, // 'hospital_name' : appointment.projectName, // 'doctor_name' : (appointment.doctorName == null || appointment.doctorName == '') ? appointment.doctorNameObj : appointment.doctorName, diff --git a/lib/services/cache_service.dart b/lib/services/cache_service.dart index 8a015d8..986baa4 100644 --- a/lib/services/cache_service.dart +++ b/lib/services/cache_service.dart @@ -95,7 +95,7 @@ class CacheServiceImp implements CacheService { if (string == null) return null; return json.decode(string); } catch (ex) { - loggerService.errorLogs(ex.toString()); + loggerService.logError(ex.toString()); return null; } } @@ -105,7 +105,7 @@ class CacheServiceImp implements CacheService { try { await sharedPreferences.setString(key, json.encode(value)); } catch (ex) { - loggerService.errorLogs(ex.toString()); + loggerService.logError(ex.toString()); } } diff --git a/lib/services/dialog_service.dart b/lib/services/dialog_service.dart index 9f1f503..b91e093 100644 --- a/lib/services/dialog_service.dart +++ b/lib/services/dialog_service.dart @@ -62,16 +62,20 @@ class DialogServiceImp implements DialogService { message: message, showCancel: onCancelPressed != null ? true : false, onOkPressed: () { + print('ok button is pressed'); if (onOkPressed != null) { + print('onOkPressed is not null'); onOkPressed(); + }else { + Navigator.pop(context); } - context.pop(); }, onCancelPressed: () { if (onCancelPressed != null) { onCancelPressed(); + }else { + Navigator.pop(context); } - context.pop(); }, ), ); @@ -211,12 +215,12 @@ Widget exceptionBottomSheetWidget({required BuildContext context, required Strin Padding( padding: EdgeInsets.only(bottom: 10.h), child: CustomButton( - text: LocaleKeys.ok.tr(), + text: LocaleKeys.cancel.tr(), onPressed: (onCancelPressed == null) ? () { context.pop(); } - : onOkPressed, + : onCancelPressed, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedBorderColor, textColor: Colors.white, diff --git a/lib/services/error_handler_service.dart b/lib/services/error_handler_service.dart index aeefefb..bda1727 100644 --- a/lib/services/error_handler_service.dart +++ b/lib/services/error_handler_service.dart @@ -25,33 +25,33 @@ class ErrorHandlerServiceImp implements ErrorHandlerService { @override Future handleError({required Failure failure, Function()? onOkPressed, Function(Failure)? onUnHandledFailure, Function(Failure)? onMessageStatusFailure}) async { if (failure is APIException) { - loggerService.errorLogs("API Exception: ${failure.message}"); + loggerService.logError("API Exception: ${failure.message}"); } else if (failure is ServerFailure) { - loggerService.errorLogs("URL: ${failure.url} \n Server Failure: ${failure.message}"); + loggerService.logError("URL: ${failure.url} \n Server Failure: ${failure.message}"); await _showDialog(failure, title: "Server Failure"); } else if (failure is DataParsingFailure) { - loggerService.errorLogs("Data Parsing Failure: ${failure.message}"); + loggerService.logError("Data Parsing Failure: ${failure.message}"); await _showDialog(failure, title: "Data Error"); } else if (failure is StatusCodeFailure) { - loggerService.errorLogs("StatusCode Failure: ${failure.message}"); + loggerService.logError("StatusCode Failure: ${failure.message}"); await _showDialog(failure, title: "StatusCodeFailure"); } else if (failure is ConnectivityFailure) { - loggerService.errorLogs("ConnectivityFailure : ${failure.message}"); + loggerService.logError("ConnectivityFailure : ${failure.message}"); await _showDialog(failure, title: "ConnectivityFailure ", onOkPressed: () {}); } else if (failure is UnAuthenticatedUserFailure) { - loggerService.errorLogs("URL: ${failure.url} \n UnAuthenticatedUser Failure: ${failure.message}"); + loggerService.logError("URL: ${failure.url} \n UnAuthenticatedUser Failure: ${failure.message}"); await _showDialog(failure, title: "UnAuthenticatedUser Failure", onOkPressed: () => navigationService.replaceAllRoutesAndNavigateToLanding()); } else if (failure is AppUpdateFailure) { - loggerService.errorLogs("AppUpdateFailure : ${failure.message}"); + loggerService.logError("AppUpdateFailure : ${failure.message}"); await _showDialog(failure, title: "AppUpdateFailure Error", onOkPressed: () => navigationService.replaceAllRoutesAndNavigateToLanding()); } else if (failure is HttpException) { - loggerService.errorLogs("Http Exception: ${failure.message}"); + loggerService.logError("Http Exception: ${failure.message}"); await _showDialog(failure, title: "Network Error"); } else if (failure is UnknownFailure) { - loggerService.errorLogs("URL: ${failure.url} \n Unknown Failure: ${failure.message}"); + loggerService.logError("URL: ${failure.url} \n Unknown Failure: ${failure.message}"); await _showDialog(failure, title: "Unknown Failure"); } else if (failure is InvalidCredentials) { - loggerService.errorLogs("Invalid Credentials : ${failure.message}"); + loggerService.logError("Invalid Credentials : ${failure.message}"); await _showDialog(failure, title: "Invalid Credentials "); } else if (failure is UserIntimationFailure) { if (onUnHandledFailure != null) { @@ -66,7 +66,7 @@ class ErrorHandlerServiceImp implements ErrorHandlerService { await _showDialog(failure, title: "MessageStatusFailure", onOkPressed: onOkPressed); } } else { - loggerService.errorLogs("Unhandled failure type: $failure"); + loggerService.logError("Unhandled failure type: $failure"); await _showDialog(failure, title: "Unhandled Error", onOkPressed: onOkPressed); } } diff --git a/lib/services/logger_service.dart b/lib/services/logger_service.dart index 9df6633..617539a 100644 --- a/lib/services/logger_service.dart +++ b/lib/services/logger_service.dart @@ -1,7 +1,7 @@ import 'package:logger/logger.dart'; abstract class LoggerService { - void errorLogs(String message); + void logError(String message); void logInfo(String message); } @@ -12,7 +12,7 @@ class LoggerServiceImp implements LoggerService { LoggerServiceImp({required this.logger}); @override - void errorLogs(String message) { + void logError(String message) { logger.e(message); } diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index b5c5e03..cb4405c 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -13,6 +13,10 @@ class NavigationService { return navigatorKey.currentState!.push(route); } + Future pushAndRemoveUntil(Route route,RoutePredicate predicate) { + return navigatorKey.currentState!.pushAndRemoveUntil(route,predicate); + } + void pop([T? result]) { navigatorKey.currentState!.pop(result); } @@ -34,6 +38,8 @@ class NavigationService { navigatorKey.currentState?.pushReplacementNamed(routeName); } + + 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)), diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index ce2f87c..6ccf8b1 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -63,6 +63,8 @@ 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); diff --git a/lib/widgets/CustomSwitch.dart b/lib/widgets/CustomSwitch.dart index 8e3364b..784e446 100644 --- a/lib/widgets/CustomSwitch.dart +++ b/lib/widgets/CustomSwitch.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; class CustomSwitch extends StatefulWidget { final bool value; @@ -17,21 +18,21 @@ class _CustomSwitchState extends State { return GestureDetector( onTap: () => widget.onChanged(!widget.value), child: Container( - width: 48.h, + width: 48.w, height: 30.h, decoration: BoxDecoration( - color: widget.value ? Color(0xFFE6F7F0) : Color(0xFFE6F7F0), + color: AppColors.switchBackgroundColor , borderRadius: BorderRadius.circular(18), ), child: AnimatedAlign( - duration: Duration(milliseconds: 200), + duration: Duration(milliseconds: 300), alignment: widget.value ? Alignment.centerRight : Alignment.centerLeft, child: Container( - margin: EdgeInsets.all(4), - width: 28, - height: 28, + margin: EdgeInsets.all(2.h), + width: 28.w, + height: 28.h, decoration: BoxDecoration( - color: Color(0xFF5FCB89), + color: AppColors.thumbColor, shape: BoxShape.circle, ), ), diff --git a/lib/widgets/appbar/app_bar_widget.dart b/lib/widgets/appbar/app_bar_widget.dart index 2c180ce..b3bc3eb 100644 --- a/lib/widgets/appbar/app_bar_widget.dart +++ b/lib/widgets/appbar/app_bar_widget.dart @@ -43,13 +43,13 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { quarterTurns: 90, child: Utils.buildSvgWithAssets( icon: AppAssets.arrow_back, - width: 32.h, + width: 32.w, height: 32.h, ), ) : Utils.buildSvgWithAssets( icon: AppAssets.arrow_back, - width: 32.h, + width: 32.w, height: 32.h, ), ), @@ -57,11 +57,7 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { ), // Logo - if (!hideLogoAndLang) - Utils.buildSvgWithAssets( - icon: AppAssets.habiblogo, - height: 24.h, - ), + if (!hideLogoAndLang) Utils.buildSvgWithAssets(icon: AppAssets.habiblogo, height: 24.h), if (!hideLogoAndLang) Expanded( diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index 1574a0a..8e07631 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: 100.h, + expandedHeight: MediaQuery.of(context).size.height * 0.12.h, stretch: true, systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Brightness.light), surfaceTintColor: Colors.transparent, @@ -110,14 +110,18 @@ 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 (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!), + 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!), + 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!, ], @@ -151,7 +155,7 @@ class CollapsingListView extends StatelessWidget { margin: EdgeInsets.only(right: 24.w), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.secondaryLightRedColor, - borderRadius: 12.r, + borderRadius: 10.r, ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, diff --git a/lib/widgets/bottom_navigation/bottom_navigation.dart b/lib/widgets/bottom_navigation/bottom_navigation.dart index 1163c66..a3e117f 100644 --- a/lib/widgets/bottom_navigation/bottom_navigation.dart +++ b/lib/widgets/bottom_navigation/bottom_navigation.dart @@ -12,9 +12,8 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; class BottomNavigation extends StatelessWidget { final int currentIndex; final ValueChanged onTap; - BuildContext? context; - BottomNavigation({super.key, required this.currentIndex, required this.onTap, this.context}); + const BottomNavigation({super.key, required this.currentIndex, required this.onTap}); @override Widget build(BuildContext context) { @@ -30,7 +29,9 @@ class BottomNavigation extends StatelessWidget { iconSize: 27, isSpecial: true, ), - appState.isAuthenticated ? BottomNavItem(icon: AppAssets.toDoBottom, label: LocaleKeys.todoList.tr(context: context)) : BottomNavItem(icon: AppAssets.news, label: LocaleKeys.news.tr()), + appState.isAuthenticated + ? BottomNavItem(icon: AppAssets.toDoBottom, label: LocaleKeys.todoList.tr(context: context)) + : BottomNavItem(icon: AppAssets.news, label: LocaleKeys.news.tr()), BottomNavItem(icon: AppAssets.servicesBottom, label: LocaleKeys.services2.tr(context: context)), ]; @@ -65,11 +66,7 @@ class BottomNavigation extends StatelessWidget { ), ), const SizedBox(height: 10), - item.label.toText12( - fontWeight: FontWeight.w500, - // color: Colors.black87, - // textAlign: TextAlign.center, - ), + item.label.toText12(fontWeight: FontWeight.w500), SizedBox(height: item.isSpecial ? 5 : 0) ], ), @@ -82,12 +79,14 @@ class BottomNavItem { final String label; final double iconSize; final bool isSpecial; + final Color? color; const BottomNavItem({ required this.icon, required this.label, this.iconSize = 21, this.isSpecial = false, + this.color, }); } diff --git a/lib/widgets/buttons/custom_button.dart b/lib/widgets/buttons/custom_button.dart index 6f33eda..b823eae 100644 --- a/lib/widgets/buttons/custom_button.dart +++ b/lib/widgets/buttons/custom_button.dart @@ -56,21 +56,25 @@ class CustomButton extends StatelessWidget { return GestureDetector( onTap: isDisabled ? null : onPressed, child: Container( - height: height ?? (56.h), + height: height == null + ? 56.h + : (isFoldable || isTablet) + ? height! + 10.h + : height, 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( @@ -82,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/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index 4d0dcf1..318751f 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -105,17 +105,18 @@ class ButtonSheetContent extends StatelessWidget { } void showCommonBottomSheetWithoutHeight( - BuildContext context, { - required Widget child, - required VoidCallback callBackFunc, - String title = "", - bool isCloseButtonVisible = true, - bool isFullScreen = true, - bool isDismissible = true, - Widget? titleWidget, - bool useSafeArea = false, + BuildContext context, { + required Widget child, + VoidCallback? callBackFunc, + String title = "", + bool isCloseButtonVisible = true, + bool isFullScreen = true, + bool isDismissible = true, + Widget? titleWidget, + bool useSafeArea = false, bool hasBottomPadding = true, Color backgroundColor = AppColors.bottomSheetBgColor, + VoidCallback? onCloseClicked }) { showModalBottomSheet( sheetAnimationStyle: AnimationStyle( @@ -157,6 +158,7 @@ void showCommonBottomSheetWithoutHeight( ), child: Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -166,12 +168,14 @@ void showCommonBottomSheetWithoutHeight( Expanded( child: title.toText20(weight: FontWeight.w600), ), - Utils.buildSvgWithAssets( - icon: AppAssets.close_bottom_sheet_icon, - iconColor: Color(0xff2B353E), - ).onPress(() { + if (isCloseButtonVisible) ...[ + Utils.buildSvgWithAssets( + icon: AppAssets.close_bottom_sheet_icon, + iconColor: Color(0xff2B353E), + ).onPress(() { + onCloseClicked?.call(); Navigator.of(context).pop(); - }), + }),], ], ), SizedBox(height: 16.h), @@ -185,7 +189,9 @@ void showCommonBottomSheetWithoutHeight( ); }, ).then((value) { - callBackFunc(); + if (callBackFunc != null) { + callBackFunc(); + } }); } diff --git a/lib/widgets/custom_tab_bar.dart b/lib/widgets/custom_tab_bar.dart index 6f8c47f..35aafa4 100644 --- a/lib/widgets/custom_tab_bar.dart +++ b/lib/widgets/custom_tab_bar.dart @@ -59,10 +59,11 @@ class CustomTabBarState extends State { if (widget.tabs.length > 3) { parentWidget = ListView.separated( + shrinkWrap: true, scrollDirection: Axis.horizontal, padding: EdgeInsets.zero, physics: const BouncingScrollPhysics(), - itemBuilder: (cxt, index) => myTab(widget.tabs[index], index).expanded, + itemBuilder: (cxt, index) => myTab(widget.tabs[index], index), separatorBuilder: (cxt, index) => 4.width, itemCount: widget.tabs.length, ); @@ -79,7 +80,7 @@ class CustomTabBarState extends State { padding: EdgeInsets.all(4.w), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, - borderRadius: 12.r, + borderRadius: 10.r, ), child: Center(child: parentWidget)); } @@ -87,12 +88,12 @@ class CustomTabBarState extends State { Widget myTab(CustomTabBarModel tabBar, int currentIndex) { bool isSelected = selectedIndex == currentIndex; return Container( - height: 54.h, + height: (isTablet || isFoldable) ? 60.h : 54.h, padding: EdgeInsets.only(top: 4.h, bottom: 4.h, left: 14.w, right: 14.w), alignment: Alignment.center, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: isSelected ? widget.activeBackgroundColor : widget.inActiveBackgroundColor, - borderRadius: 12.r, + borderRadius: 10.r, ), child: Row( mainAxisSize: MainAxisSize.min, diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index 2dcd32f..d943731 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -113,7 +113,7 @@ class TextInputWidget extends StatelessWidget { children: [ Container( padding: padding, - height: 58.h, + height: 64.h, alignment: Alignment.center, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: Colors.white, diff --git a/lib/widgets/language_switcher.dart b/lib/widgets/language_switcher.dart index ee54015..91e45b2 100644 --- a/lib/widgets/language_switcher.dart +++ b/lib/widgets/language_switcher.dart @@ -42,7 +42,7 @@ class _LanguageSelectorState extends State { return Stack(clipBehavior: Clip.none, children: [ Container( decoration: BoxDecoration( - borderRadius: BorderRadius.circular((12.r)), + borderRadius: BorderRadius.circular((10.r)), color: AppColors.secondaryLightRedColor, ), child: InkWell( diff --git a/lib/widgets/map/HMSMap.dart b/lib/widgets/map/HMSMap.dart index 852af41..f655479 100644 --- a/lib/widgets/map/HMSMap.dart +++ b/lib/widgets/map/HMSMap.dart @@ -1,41 +1,63 @@ import 'dart:async'; 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:huawei_map/huawei_map.dart' ; class HMSMap extends StatefulWidget{ final CameraPosition currentLocation; final Function(CameraPosition) onCameraMoved; + final VoidCallback onCameraIdle; final MapType mapType; final bool compassEnabled; final bool myLocationEnabled; + final bool showCenterMarker; + final Completer? inputController; - HMSMap({super.key, required this.currentLocation ,required this.onCameraMoved, this.mapType = MapType.normal,this.compassEnabled = false, this.myLocationEnabled = true}); + HMSMap({super.key, required this.currentLocation ,required this.onCameraMoved,required this.onCameraIdle, + this.mapType = MapType.normal,this.compassEnabled = false,this.showCenterMarker = false, + this.myLocationEnabled = true, this.inputController}); @override State createState() => _HMSMapState(); } class _HMSMapState extends State { - final Completer _controller = Completer(); + Completer? controller; @override void initState() { HuaweiMapInitializer.initializeMap(); + controller = widget.inputController ?? Completer(); super.initState(); } // @override @override Widget build(BuildContext context) => - HuaweiMap( - mapType: widget.mapType, - zoomControlsEnabled: false, - myLocationEnabled: widget.myLocationEnabled, - compassEnabled: widget.compassEnabled, - initialCameraPosition: widget.currentLocation, - onCameraMove: (value) => widget.onCameraMoved(value), - onMapCreated: (HuaweiMapController controller) { - _controller.complete(controller); - }, - ); + Stack( + children: [ + HuaweiMap( + mapType: widget.mapType, + zoomControlsEnabled: false, + myLocationEnabled: widget.myLocationEnabled, + myLocationButtonEnabled: false, + compassEnabled: widget.compassEnabled, + onCameraIdle:()=> widget.onCameraIdle(), + initialCameraPosition: widget.currentLocation, + onCameraMove: (value) => widget.onCameraMoved(value), + onMapCreated: (HuaweiMapController controller) { + this.controller?.complete(controller); + }, + ), + Visibility( + visible: widget.showCenterMarker, + child: Align( + alignment: Alignment.center, + child: Utils.buildSvgWithAssets(icon: AppAssets.pin_location, width: 24.w, height: 36.h), + ), + ) + ], + ); } \ No newline at end of file diff --git a/lib/widgets/map/map.dart b/lib/widgets/map/map.dart index cd06838..0c67f1b 100644 --- a/lib/widgets/map/map.dart +++ b/lib/widgets/map/map.dart @@ -2,31 +2,52 @@ import 'dart:async'; import 'package:flutter/material.dart'; 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'; class GMSMap extends StatelessWidget{ - final Completer _controller = Completer(); + Completer? controller; final CameraPosition currentLocation; final Function(CameraPosition) onCameraMoved; + final VoidCallback onCameraIdle; final MapType mapType; final bool compassEnabled; final bool myLocationEnabled; + final bool showCenterMarker; - GMSMap({super.key, required this.currentLocation ,required this.onCameraMoved, this.mapType = MapType.normal,this.compassEnabled = false, this.myLocationEnabled = true}); + GMSMap({super.key, required this.currentLocation ,required this.onCameraMoved,required this.onCameraIdle, + this.mapType = MapType.normal,this.compassEnabled = false,this.showCenterMarker = false, + this.myLocationEnabled = true,Completer? inputController}){ + controller = inputController ?? Completer(); + } @override Widget build(BuildContext context) { - return GoogleMap( - mapType: mapType, - zoomControlsEnabled: false, - myLocationEnabled: myLocationEnabled, - compassEnabled: compassEnabled, - initialCameraPosition: currentLocation, - onCameraMove: (value) => onCameraMoved(value), - onMapCreated: (GoogleMapController controller) { - - _controller.complete(controller); - }, + return Stack( + children: [ + GoogleMap( + mapType: mapType, + zoomControlsEnabled: false, + myLocationEnabled: myLocationEnabled, + myLocationButtonEnabled: false, + compassEnabled: compassEnabled, + initialCameraPosition: currentLocation, + onCameraMove: (value) => onCameraMoved(value), + onCameraIdle: ()=>onCameraIdle(), + onMapCreated: (GoogleMapController controller) { + this.controller?.complete(controller); + }, + ), + Visibility( + visible: showCenterMarker, + child: Align( + alignment: Alignment.center, + child: Utils.buildSvgWithAssets(icon: AppAssets.pin_location, width: 24.w, height: 36.h), + ), + ) + ], ); } } \ No newline at end of file diff --git a/lib/widgets/order_tracking/order_tracking_state.dart b/lib/widgets/order_tracking/order_tracking_state.dart index d33e762..5389488 100644 --- a/lib/widgets/order_tracking/order_tracking_state.dart +++ b/lib/widgets/order_tracking/order_tracking_state.dart @@ -3,4 +3,6 @@ enum OrderTrackingState { dispactched, returning, ended, + failed, + cancel } \ No newline at end of file diff --git a/lib/widgets/order_tracking/request_tracking.dart b/lib/widgets/order_tracking/request_tracking.dart index d25d8bc..c0847f9 100644 --- a/lib/widgets/order_tracking/request_tracking.dart +++ b/lib/widgets/order_tracking/request_tracking.dart @@ -5,7 +5,7 @@ import 'package:hmg_patient_app_new/widgets/order_tracking/order_tracking_state. class OrderTrackingWidget extends StatelessWidget { final double? height; - final OrderTrackingState state; + final OrderTrackingState? state; final double parentPaddingValue; const OrderTrackingWidget({super.key, this.height, required this.state, required this.parentPaddingValue}); @@ -23,7 +23,7 @@ class OrderTrackingWidget extends StatelessWidget { ); } - Widget? getBarAndDot(OrderTrackingState state, BuildContext context, + Widget? getBarAndDot(OrderTrackingState? state, BuildContext context, double width) { print("the parent padding value is $parentPaddingValue"); print("the parent padding value is ${(parentPaddingValue+24)}"); @@ -31,6 +31,16 @@ class OrderTrackingWidget extends StatelessWidget { double widthOfOneState = (width/4) - (parentPaddingValue+8.h); print("te width of one state is $widthOfOneState"); print("the value of${width/4}"); + if(state == null) { + return Row( + children: List.generate(4, (index) { + if (index == 0) { + return oneProgressBar(widthOfOneState, AppColors.primaryRedColor, true); + } else { + return oneProgressBar(widthOfOneState, AppColors.greyLightColor, false); + } + })); + } switch (state) { case OrderTrackingState.waitingForCall: return Row( @@ -73,6 +83,26 @@ class OrderTrackingWidget extends StatelessWidget { // } }) ); + case OrderTrackingState.failed: + return Row( + children: List.generate(4, (index){ + // if(index == 3) { + return oneProgressBar(widthOfOneState, AppColors.errorColor, index == 3); + // } else { + // return oneProgressBar(widthOfOneState, AppColors.greyLightColor, false); + // } + }) + ); + case OrderTrackingState.cancel: + return Row( + children: List.generate(4, (index){ + // if(index == 3) { + return oneProgressBar(widthOfOneState, AppColors.errorColor, index == 3); + // } else { + // return oneProgressBar(widthOfOneState, AppColors.greyLightColor, false); + // } + }) + ); } } diff --git a/lib/widgets/routes/custom_page_route.dart b/lib/widgets/routes/custom_page_route.dart index 475b2ee..b89d710 100644 --- a/lib/widgets/routes/custom_page_route.dart +++ b/lib/widgets/routes/custom_page_route.dart @@ -7,8 +7,10 @@ class CustomPageRoute extends PageRouteBuilder { final AxisDirection direction; final bool fullScreenDialog; - CustomPageRoute({required this.page, this.direction = AxisDirection.right, this.fullScreenDialog = false}) + + CustomPageRoute({required this.page, this.direction = AxisDirection.right, this.fullScreenDialog = false, super.settings}) : super( + transitionDuration: const Duration(milliseconds: 1500), reverseTransitionDuration: const Duration(milliseconds: 500), fullscreenDialog: fullScreenDialog,