Merge branch 'master' of http://34.17.182.140/Haroon6138/HMG_Patient_App_New into notification_center_id_Dev_sultan

* 'master' of http://34.17.182.140/Haroon6138/HMG_Patient_App_New: (73 commits)
  updates
  Updates
  Dynamic theming has been implemented
  Completed THE Theme changes
  theme code updated.
  MyFatoorah implementation contd.
  MyFatoorah Payment integration in progress
  updates
  updates
  Updates
  Update to stores Version 22.1
  Updates
  Locale fix
  Update to Stores VersionID 22.0
  Insurance model updates
  Habib Wallet & Lakum data updates
  Habib Wallet & Lakum card updates
  Dubai timezone handled
  CMC summary page updated
  Medical report date fix
  ...
notification_center_id_Dev_sultan
Sultan khan 6 days ago
commit 2fcbf71189

@ -36,7 +36,7 @@ android {
applicationId = "com.cloudsolutions.HMGPatientApp"
// minSdk = 24
minSdk = 29
targetSdk = 35
targetSdk = 36
compileSdk = 36
// targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode

@ -0,0 +1,26 @@
<resources>
<color name="payment_sdk_primary_color">#ffffff</color>
<color name="payment_sdk_secondary_color">#ED1C2B</color>
<color name="payment_sdk_status_bar_color">#444647</color>
<color name="payment_sdk_primary_font_color">#2E3039</color>
<color name="payment_sdk_secondary_font_color">#2E3039</color>
<color name="payment_sdk_hint_font_color">#a5a5a5</color>
<color name="payment_sdk_stroke_color">#e1e1e1</color>
<color name="payment_sdk_button_text_color">#FFF</color>
<color name="payment_sdk_title_text_color">#1e1e1e</color>
<color name="payment_sdk_button_background_color">#ED1C2B</color>
<color name="payment_sdk_background_color">#F9FAFD</color>
<color name="payment_sdk_blue_F2FAFD">#F2FAFD</color>
<color name="payment_sdk_error_text_color">#EC2213</color>
<color name="payment_sdk_back_black_dim">#4D6E6E6E</color>
<color name="payment_sdk_input_field_background_color">#FFFFFFFF</color>
<color name="payment_sdk_enabled_switch_track_color">#00000000</color>
<color name="payment_sdk_enabled_switch_handle_color">#3db39e</color>
<color name="payment_sdk_disabled_switch_track_color">#00000000</color>
<color name="payment_sdk_disabled_switch_handle_color">#c7c7c7</color>
<color name="payment_sdk_switch_stroke_color">#4c4c4c</color>
<color name="payment_sdk_amount_font_color">#4c4c4c</color>
<color name="payment_sdk_original_amount_font_color">#a5a5a5</color>
<color name="payment_sdk_billing_header_background_color">#ED1C2B</color>
<color name="payment_sdk_billing_text_color">#FFF</color>
</resources>

@ -2,4 +2,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
#distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-all.zip

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="120" height="120" fill="#EFF1F3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.2503 38.4816C33.2603 37.0472 34.4199 35.8864 35.8543 35.875H83.1463C84.5848 35.875 85.7503 37.0431 85.7503 38.4816V80.5184C85.7403 81.9528 84.5807 83.1136 83.1463 83.125H35.8543C34.4158 83.1236 33.2503 81.957 33.2503 80.5184V38.4816ZM80.5006 41.1251H38.5006V77.8751L62.8921 53.4783C63.9172 52.4536 65.5788 52.4536 66.6039 53.4783L80.5006 67.4013V41.1251ZM43.75 51.6249C43.75 54.5244 46.1005 56.8749 49 56.8749C51.8995 56.8749 54.25 54.5244 54.25 51.6249C54.25 48.7254 51.8995 46.3749 49 46.3749C46.1005 46.3749 43.75 48.7254 43.75 51.6249Z" fill="#687787"/>
</svg>

After

Width:  |  Height:  |  Size: 892 B

@ -0,0 +1,12 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.9384 3.88371e-06C3.28428 -5.8582e-05 2.69656 -0.000114493 2.22085 0.0638424C1.70149 0.133668 1.16868 0.295802 0.732241 0.732241C0.295802 1.16868 0.133668 1.70149 0.0638424 2.22085C-0.000114493 2.69656 -5.8582e-05 3.28428 3.88371e-06 3.9384L3.88371e-06 4.06161C-5.8582e-05 4.71574 -0.000114493 5.30346 0.0638424 5.77916C0.133668 6.29852 0.295802 6.83134 0.732241 7.26777C1.16868 7.70421 1.70149 7.86635 2.22085 7.93617C2.69656 8.00013 3.28427 8.00007 3.9384 8.00001H4.06162C4.71574 8.00007 5.30346 8.00013 5.77916 7.93617C6.29852 7.86635 6.83134 7.70421 7.26777 7.26777C7.70421 6.83134 7.86635 6.29852 7.93617 5.77916C8.00013 5.30346 8.00007 4.71574 8.00001 4.06162V3.9384C8.00007 3.28427 8.00013 2.69656 7.93617 2.22085C7.86635 1.70149 7.70421 1.16868 7.26777 0.732241C6.83134 0.295802 6.29852 0.133668 5.77916 0.0638424C5.30346 -0.000114493 4.71574 -5.8582e-05 4.06161 3.88371e-06L3.9384 3.88371e-06Z" fill="#8F9AA3"/>
<path d="M3.9384 12C3.28428 11.9999 2.69655 11.9999 2.22085 12.0638C1.70149 12.1337 1.16868 12.2958 0.732241 12.7322C0.295802 13.1687 0.133668 13.7015 0.0638424 14.2209C-0.000114493 14.6966 -5.8582e-05 15.2843 3.88371e-06 15.9384L3.88371e-06 16.0616C-5.8582e-05 16.7157 -0.000114493 17.3035 0.0638424 17.7792C0.133668 18.2985 0.295802 18.8313 0.732241 19.2678C1.16868 19.7042 1.70149 19.8663 2.22085 19.9362C2.69656 20.0001 3.28427 20.0001 3.9384 20H4.06162C4.71574 20.0001 5.30346 20.0001 5.77916 19.9362C6.29852 19.8663 6.83134 19.7042 7.26777 19.2678C7.70421 18.8313 7.86635 18.2985 7.93617 17.7792C8.00013 17.3035 8.00007 16.7157 8.00001 16.0616V15.9384C8.00007 15.2843 8.00013 14.6966 7.93617 14.2209C7.86635 13.7015 7.70421 13.1687 7.26777 12.7322C6.83134 12.2958 6.29852 12.1337 5.77916 12.0638C5.30346 11.9999 4.71575 11.9999 4.06163 12H3.9384Z" fill="#8F9AA3"/>
<path d="M0 10C0 9.44771 0.447715 9 1 9L7 9C7.55228 9 8 9.44772 8 10C8 10.5523 7.55228 11 7 11L1 11C0.447715 11 0 10.5523 0 10Z" fill="#8F9AA3"/>
<path d="M11 1C11 0.447715 10.5523 0 10 0C9.44771 0 9 0.447715 9 1L9 6C9 6.55228 9.44771 7 10 7C10.5523 7 11 6.55228 11 6L11 1Z" fill="#8F9AA3"/>
<path d="M15.9384 3.88371e-06L16.0616 3.88371e-06C16.7157 -5.8582e-05 17.3035 -0.000114493 17.7792 0.0638424C18.2985 0.133668 18.8313 0.295802 19.2678 0.732241C19.7042 1.16868 19.8663 1.70149 19.9362 2.22085C20.0001 2.69656 20.0001 3.28427 20 3.9384V4.06162C20.0001 4.71574 20.0001 5.30346 19.9362 5.77916C19.8663 6.29852 19.7042 6.83134 19.2678 7.26777C18.8313 7.70421 18.2985 7.86635 17.7792 7.93617C17.3035 8.00013 16.7157 8.00007 16.0616 8.00001H15.9384C15.2843 8.00007 14.6966 8.00013 14.2209 7.93617C13.7015 7.86635 13.1687 7.70421 12.7322 7.26777C12.2958 6.83134 12.1337 6.29852 12.0638 5.77916C11.9999 5.30346 11.9999 4.71575 12 4.06163V3.9384C11.9999 3.28428 11.9999 2.69655 12.0638 2.22085C12.1337 1.70149 12.2958 1.16868 12.7322 0.732241C13.1687 0.295802 13.7015 0.133668 14.2209 0.0638424C14.6966 -0.000114493 15.2843 -5.8582e-05 15.9384 3.88371e-06Z" fill="#8F9AA3"/>
<path d="M12.9384 9C12.2843 8.99994 11.6966 8.99989 11.2209 9.06384C10.7015 9.13367 10.1687 9.2958 9.73224 9.73224C9.2958 10.1687 9.13367 10.7015 9.06384 11.2209C8.99989 11.6966 8.99994 12.2843 9 12.9384L9.00001 13C9.00001 13.5523 9.44772 14 10 14C10.5523 14 11 13.5523 11 13C11 12.2646 11.0021 11.8137 11.046 11.4873C11.0846 11.2005 11.1394 11.1526 11.1459 11.1469L11.1469 11.1459C11.1526 11.1394 11.2005 11.0846 11.4873 11.046C11.8137 11.0021 12.2646 11 13 11L19 11C19.5523 11 20 10.5523 20 10C20 9.44772 19.5523 9.00001 19 9.00001L12.9384 9Z" fill="#8F9AA3"/>
<path d="M16 14C16.7354 14 17.1862 14.0021 17.5125 14.0461C17.799 14.0846 17.8468 14.1395 17.8526 14.146L17.8536 14.1471C17.8604 14.1531 17.9154 14.2016 17.954 14.4891C17.9979 14.8162 18 15.268 18 16.0043C18 16.7407 17.9979 17.1925 17.954 17.5196C17.9154 17.8071 17.8604 17.8556 17.8536 17.8616L17.8522 17.863C17.8442 17.8712 17.8181 17.8979 17.7034 17.9272C17.5612 17.9636 17.3416 17.9892 16.9698 18.0005C16.4178 18.0171 15.9838 18.4781 16.0005 19.0302C16.0171 19.5822 16.4781 20.0162 17.0302 19.9996C17.7921 19.9766 18.64 19.9045 19.2683 19.2753C19.7045 18.8385 19.8664 18.3054 19.9362 17.7857C20.0001 17.3095 20.0001 16.7211 20 16.066V15.9428C20.0001 15.2876 20.0001 14.6992 19.9362 14.223C19.8664 13.7033 19.7045 13.1702 19.2683 12.7334C18.8319 12.2964 18.299 12.1339 17.7794 12.0639C17.3036 11.9999 16.7157 11.9999 16.0617 12L16 12C15.4477 12 15 12.4477 15 13C15 13.5523 15.4477 14 16 14Z" fill="#8F9AA3"/>
<path d="M14 13C14 12.4477 13.5523 12 13 12C12.4477 12 12 12.4477 12 13V14.5C12 15.394 12.2542 16.2178 13.0565 16.6656C13.3946 16.8543 13.7549 16.928 14.0621 16.9627C14.3698 16.9975 14.6995 17 15 17C15 17.5523 15.4477 18 16 18C16.5523 18 17 17.5523 17 17C17 15.8954 16.1046 15 15 15C14.6924 15 14.468 14.9958 14.2866 14.9753C14.152 14.9601 14.0822 14.9397 14.051 14.9279C14.0333 14.8814 14 14.7576 14 14.5V13Z" fill="#8F9AA3"/>
<path d="M11 15.7692C11 15.217 10.5523 14.7692 10 14.7692C9.44772 14.7692 9.00001 15.217 9.00001 15.7692L9.00001 18.5385C9.00001 19.0908 9.44772 19.5385 10 19.5385C10.5523 19.5385 11 19.0908 11 18.5385L11 15.7692Z" fill="#8F9AA3"/>
<path d="M13 18C12.4477 18 12 18.4477 12 19C12 19.5523 12.4477 20 13 20H14C14.5523 20 15 19.5523 15 19C15 18.4477 14.5523 18 14 18H13Z" fill="#8F9AA3"/>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

@ -0,0 +1,7 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="10" fill="#EFEFF0"/>
<g transform="translate(8.8, 8.8) scale(1.6)">
<path d="M11.5658 1.95717C9.82689 0.890531 8.26692 1.31563 7.32452 2.02335C7.26938 2.06476 7.22039 2.10153 7.17659 2.13413C7.08676 2.20099 7.04185 2.23442 6.98387 2.23442C6.92589 2.23442 6.88098 2.20099 6.79115 2.13413C6.74735 2.10153 6.69836 2.06476 6.64322 2.02335C5.70082 1.31563 4.14085 0.890531 2.40195 1.95717C1.53619 2.48823 0.943044 3.43564 0.767402 4.5853C0.741775 4.75304 0.728961 4.83691 0.781341 4.89789C0.83372 4.95888 0.923033 4.95887 1.10166 4.95885L3.00095 4.95868C3.24133 4.95767 3.64619 4.95596 4.03964 5.0991C4.19392 5.15522 4.33336 5.22877 4.4573 5.3094C4.63656 5.42602 4.72618 5.48433 4.79864 5.46761C4.87109 5.45089 4.92018 5.36908 5.01835 5.20545L5.44147 4.50026C5.71062 4.05168 6.19943 3.78162 6.72246 3.79256C7.24549 3.80349 7.72259 4.09374 7.97275 4.55319L8.72948 5.94294C8.77795 6.03196 8.80218 6.07647 8.8435 6.10102C8.88482 6.12557 8.9355 6.12557 9.03686 6.12557H9.31698C10.1224 6.12557 10.7753 6.77849 10.7753 7.5839C10.7753 8.38932 10.1224 9.04224 9.31698 9.04224L8.66574 9.04348C8.42373 9.04908 7.69714 9.06588 7.06184 8.57794C7.0432 8.56363 7.02501 8.54906 7.00725 8.5343C6.81047 8.37075 6.71208 8.28898 6.63276 8.30247C6.55344 8.31596 6.49635 8.41111 6.38218 8.6014L6.19249 8.91754C5.93991 9.33851 5.49248 9.60414 5.00197 9.62434C4.51146 9.64453 4.0437 9.41658 3.75738 9.0178L3.04199 8.02146C2.99064 7.94995 2.96497 7.91419 2.92736 7.8949C2.88975 7.8756 2.84573 7.8756 2.75769 7.8756L1.88603 7.87559C1.63453 7.87558 1.50878 7.87558 1.45819 7.96269C1.40759 8.0498 1.46788 8.15548 1.58846 8.36685C2.2106 9.45742 3.18686 10.5632 4.58832 11.6005C5.43495 12.2275 6.05634 12.6877 6.98387 12.6877C7.91141 12.6877 8.53279 12.2275 9.37943 11.6005C11.8175 9.79586 12.9688 7.78416 13.2073 6.00548C13.4447 4.23535 12.7682 2.69472 11.5658 1.95717Z" fill="#8F9AA3"/>
<path d="M7.20469 4.97105C7.10463 4.78727 6.91379 4.67117 6.70458 4.66679C6.49537 4.66242 6.29984 4.77044 6.19218 4.94988L4.8987 7.10568L4.50819 6.5618C4.50002 6.55043 4.49145 6.53935 4.48248 6.52859C4.47338 6.51766 4.4634 6.50547 4.45255 6.49222C4.32787 6.33991 4.08822 6.04716 3.74091 5.92081C3.49697 5.83206 3.23003 5.83265 2.965 5.83324L2.90072 5.83333H1.73405C1.41188 5.83333 1.15072 6.0945 1.15072 6.41667C1.15072 6.73883 1.41188 7 1.73405 7H2.90072C3.27245 7 3.31696 7.00823 3.34111 7.01684C3.34329 7.01792 3.36229 7.02776 3.40145 7.06546C3.45005 7.11226 3.4979 7.16947 3.57276 7.25931L4.46854 8.50689C4.58307 8.6664 4.77018 8.75758 4.96638 8.74951C5.16258 8.74143 5.34156 8.63518 5.44259 8.46679L6.66782 6.42473L7.16648 7.34054C7.17206 7.35078 7.17794 7.36086 7.18412 7.37075L7.20842 7.40983C7.30282 7.56206 7.41744 7.74688 7.59523 7.88343C7.98366 8.18176 8.4448 8.17225 8.67006 8.16761C8.69423 8.16711 8.71569 8.16667 8.73405 8.16667H9.31738C9.63955 8.16667 9.90072 7.9055 9.90072 7.58333C9.90072 7.26117 9.63955 7 9.31738 7H8.73405C8.69863 7 8.66723 7.00017 8.63892 7.00032C8.53614 7.00086 8.47421 7.00119 8.40933 6.99253C8.35288 6.98499 8.32768 6.97346 8.31135 6.96218C8.30864 6.95892 8.30403 6.95313 8.29726 6.94384C8.27206 6.90926 8.24113 6.86063 8.18271 6.76722L7.20469 4.97105Z" fill="#8F9AA3"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

@ -0,0 +1,12 @@
{
"key": "none",
"enabled": false,
"start_date": null,
"end_date": null,
"primary_color": null,
"secondary_color": null,
"background_opacity_image": null,
"background_image_opacity": 0.2,
"header_image": null
}

@ -123,7 +123,7 @@
"forgotFileNoTitle": "نسيت الملف الطبي",
"enterNationalId": "يرجى إدخال رقم الهاتف المحمول ورقم الهوية",
"profileInfo": "يرجى إدخال معلومات الملف الشخصي",
"submit": "بحث",
"submit": "تأكيد",
"forgotDesc": "أدخل رقم الهاتف المحمول لاستلام رقم الملف الطبي عبر الرسائل القصيرة",
"dob": "تاريخ الميلاد",
"hijriDate": "التاريخ الهجري",
@ -1390,7 +1390,6 @@
"medicalFiles": "الملفات الطبية",
"addANewFamilyMember": "اضافة",
"viewInvoiceDetails": "عرض تفاصيل الفاتورة",
"outPatient": "مريض خارجي",
"invoiceDetails": "تفاصيل الفاتورة",
"sendingEmailPleaseWait": "جاري إرسال ال<D8A7><D984>ريد الإلكتروني، يرجى الانتظار...",
"emailSentSuccessfullyMessage": "تم إرسال البريد الإلكتروني بنجاح.",
@ -1946,7 +1945,7 @@
"transactions": "المعاملات",
"pharmacy": "الصيدلية",
"visitsOrders": "الزيارات/الطلبات",
"earned": "حصل",
"earned": "مكتسب",
"getParkingQR": "الحصول على رمز الاستجابة السريعة لوقوف السيارات",
"cannotOpenThisFile": "لا يمكن فتح هذا الملف",
"refundDetails": "تفاصيل الاسترداد",
@ -1960,12 +1959,96 @@
"refund": "استرداد",
"parkingQR": "رمز الاستجابة السريعة لوقوف السيارات",
"parkingQRDesc": "امسح رمز الاستجابة السريعة (QR Code) أدناه عند بوابة الخروج من موقف السيارات:",
"livecareUpdateInsuranceSent": "تم استلام طلب تحديث بيانات التأمين الخاص بك، ويجري العمل عليه حالياً. سنقوم بإشعارك فور اكتمال التحديث.",
"livecareUpdateInsurance": "سيتم تحويل طلب تحديث بيانات التأمين إلى فريق LiveCare المختص لاستكمال الإجراءات. هل تود المتابعة؟",
"schedule": "جدول",
"doctorSchedule": "جدول الطبيب",
"weeklyShift": "الفترات الأسبوعية",
"dayOff": "إجازة",
"notAvailable": "غير متاح",
"hide": "إخفاء"
"livecareUpdateInsuranceSent": "تم استلام طلب تحديث بيانات التأمين الخاص بك، ويجري العمل عليه حالياً. سنقوم بإشعارك فور اكتمال التحديث.",
"livecareUpdateInsurance": "سيتم تحويل طلب تحديث بيانات التأمين إلى فريق LiveCare المختص لاستكمال الإجراءات. هل تود المتابعة؟",
"schedule": "جدول",
"doctorSchedule": "جدول الطبيب",
"weeklyShift": "الفترات الأسبوعية",
"dayOff": "إجازة",
"notAvailable": "غير متاح",
"hide": "إخفاء",
"viewPatientQR": "عرض رمز الاستجابة السريعة للمريض",
"patientQR": "رمز الاستجابة السريعة للمريض",
"forText": "عن",
"liveCareTermsHeading": "شروط خدمات الرعاية الصحية عن بُعد",
"liveCareTermsIntroHeading": "مقدمة",
"liveCareTermsConditions1": "تعتبر شركة مجموعة الدكتور سليمان الحبيب للخدمات الطبية ('المجموعة') واحدة من الشركات الرائدة في قطاع الرعاية الصحية في المملكة العربية السعودية والتي تسعى دائمًا لتحسين خدماتها في المنطقة، وكخطوة لنقل خدماتنا إلى مستوى آخر ولتسهيل عملية تلقي خدمات الرعاية الصحية، فقد اعتمدنا نهج تقديم بعض خدماتنا عن بُعد للمرضى. تنطبق هذه الشروط على خدمات الرعاية الصحية عن بُعد التي نقدمها، والتي تتيح إجراء الاستشارات بين الطبيب والمريض عبر الإنترنت، باستخدام تقنية الاتصالات الصوتية أو المرئية (يشار إليها غالباً باسم 'خدمات الرعاية الصحية عن بُعد').",
"liveCareTermsConditions2": "الغرض من خدمات الرعاية الصحية عن بعد هو تسهيل الاتصالات بين الممارسين الصحيين والمرضى لتقديم خدمات الرعاية الصحية من خلال وسائل الاتصال الإلكترونية المتقدمة، والتي تتيح للممارسين الصحيين تقييم وتشخيص وعلاج ومتابعة المرضى عن بُعد دون الحاجة إلى حضور المرضى شخصياً.",
"liveCareTermsHeading2": "الاتفاقية",
"liveCareTermsConditions3": "يتم تقديم خدمات الرعاية الصحية عن بُعد عبر تطبيق الهاتف المحمول أو الموقع الإلكتروني للمجموعة. تنطبق الشروط التالية على خدمات الرعاية الصحية عن بُعد: ",
"liveCareTermsConditions3a": "شروط استخدام الموقع الالكتروني - ",
"liveCareTermsConditions3b": "تفاقية مستخدم التطبيق - ",
"liveCareTermsConditions3c": "روط استخدام الخدمات الإلكترونية - ",
"liveCareTermsConditions3d": "شروط خدمات الرعاية الصحية عن بُعد هذه - ",
"liveCareTermsConditions4": "بالموافقة على الاستمرار في خدمات الرعاية الصحية عن بُعد وقبول هذه الشروط، فإنك تدخل في اتفاقية مع المجموعة تتضمن جميع الشروط المذكورة أعلاه (اتفاقية خدمات الرعاية الصحية عن بُعد).",
"liveCareTermsConditions5": "يتعين عليك أيضاً قراءة سياسة الخصوصية الخاصة بنا حيث تحتوي سياسة الخصوصية على تفاصيل حول كيفية استخدامنا لمعلوماتك الشخصية وحقوقك بشأنها. من خلال متابعة استخدام خدمات الرعاية الصحية عن بُعد، فإنك توافق على استخدامنا لمعلوماتك الشخصية وفقاً لسياسة الخصوصية الخاصة بنا وتوافق صراحةً على استخدامنا لمعلوماتك الشخصية لأداء خدمات الرعاية الصحية عن بُعد وأداء واجباتنا المهنية كمقدم لخدمات الرعاية الطبية.",
"liveCareTermsConditions6": "يتعين عليك أيضاً قراءة سياسة الخصوصية الخاصة بنا حيث تحتوي سياسة الخصوصية على تفاصيل حول كيفية استخدامنا لمعلوماتك الشخصية وحقوقك بشأنها. من خلال متابعة استخدام خدمات الرعاية الصحية عن بُعد، فإنك توافق على استخدامنا لمعلوماتك الشخصية وفقاً لسياسة الخصوصية الخاصة بنا وتوافق صراحةً على استخدامنا لمعلوماتك الشخصية لأداء خدمات الرعاية الصحية عن بُعد وأداء واجباتنا المهنية كمقدم لخدمات الرعاية الطبية.",
"liveCareTermsHeading3": "1.\tالتعاريف",
"liveCareTermsConditions7": "المجموعة تعني شركة مجموعة الدكتور سليمان الحبيب للخدمات الطبية والشركات التابعة لها.",
"liveCareTermsConditions8": "خدمات الرعاية الصحية عن بُعد تعني خدمة الرعاية الصحية عن بُعد التي تقدمها المجموعة، والتي تتيح الاستشارة والتشخيص والمتابعة عن بُعد بين المريض والطبيب، باستخدام التطبيق و/أو موقع الويب.",
"liveCareTermsConditions9": "خدماتنا تعني بعض الخدمات التي تقدمها المجموعة والتي تغطي استعراض السجلات الطبية للمرضى ونتائج الفحوصات الطبية، وتوفير التشخيصات الطبية بالإضافة إلى توفر خدمة الرعاية الصحية عن بعد بالصوت والفيديو بين ممارسي الرعاية الصحية والمرضى.",
"liveCareTermsConditions10": "التطبيق يعني تطبيق (Dr. ALHABIB) وهو عبارة عن منصة يتم من خلالها تقديم خدماتنا ويمكن للمستخدمين الولوج إليها.",
"liveCareTermsConditions11": "موقع الويب يعني موقع الويب الخاص بالمجموعة وهو عبارة عن منصة يتم من خلالها تقديم خدماتنا ويمكن للمستخدمين الدخول إليها.",
"liveCareTermsConditions12": "الشروط والأحكام تعني شروط خدمات الرعاية الصحية عن بُعد هذه.",
"liveCareTermsConditions13": "المحتوى يعني أي رسالة أو بيانات أو معلومات أو نص أو صوت أو صور أو فيديو أو رسومات أو رمز أو أي مادة أخرى واردة في التطبيق أو موقع الويب أو يتم نقلها عبر التطبيق أو موقع الويب.",
"liveCareTermsHeading4": "2.\tتعهدات المستخدم",
"liveCareTermsConditions14": "2-1\tمن خلال دخولك إلى خدمات الرعاية الصحية عن بُعد واستخدامها، فإنك توافق على هذه الشروط والأحكام ويتعين عليك ألا تحاول الدخول إلى خدماتنا إذا كنت لا توافق عليها.",
"liveCareTermsConditions15": "2-2\tأنت تضمن وتقر وتتعهد بأن جميع المعلومات التي تقدمها عن نفسك وعن أي شخص آخر صحيحة ودقيقة.",
"liveCareTermsConditions16": "2-3\tخدمات الرعاية الصحية عن بعد مخصصة للاستخدام من قبل المرضى الموجودين فعلياً في المملكة العربية السعودية (المنطقة المسموح بها) ولا ينبغي استخدامها من قبل المرضى خارج المنطقة المسموح بها. بالموافقة على هذه الشروط والأحكام والدخول إلى خدمات الرعاية الصحية عن بُعد عبر التطبيق أو موقع الويب، فإنك تقر وتضمن لنا أنك موجود فعلياً في إحدى المناطق المسموح بها.",
"liveCareTermsHeading5": "3.\tالقيود على خدمات الرعاية الصحية عن بعد والمخاطر المرتبطة بها",
"liveCareTermsConditions17": "3-1\tلا تستخدم التطبيق أو موقع الويب أو خدمات الرعاية الصحية عن بُعد للتواصل بشأن الحالات الطبية الطارئة أو علاجها. لا التطبيق ولا موقع الويب ولا خدمات الرعاية الصحية عن بُعد توفر الرعاية في الحالات الطبية الطارئة. إذا كانت هناك حالة طبية طارئة عاجلة أو كانت هناك حاجة إلى استجابة سريعة فيما يتعلق بصحتك، فانتقل إلى أقرب مركز/مستشفى طوارئ أو اتصل على الفور بأي رقم هاتف للطوارئ.",
"liveCareTermsConditions18": "3-2\tخدمات الرعاية الصحية عن بعد ليست مناسبة للحالات الطبية الطارئة وقد تكون هناك بعض العلاجات أو الأدوية التي لا يمكن تقديمها دون استشارة متابعة في حضور المريض شخصياً. إذا كنت تعتقد أن لديك حالة طبية خطيرة، فعليك طلب استشارة شخصية مع أخصائي طبي مرخص.",
"liveCareTermsConditions19": "3-3\tتهدف خدمات الرعاية الصحية عن بُعد إلى توفير مزايا تشمل راحة المريض. لذلك، فهو قد يكون الأنسب كبديل للاستشارة الشخصية للأمور الطبية الروتينية. ومع ذلك، هناك مخاطر محتملة مرتبطة باستخدام أي خدمة للرعاية الصحية عن بُعد، بما في ذلك خدمات الرعاية الصحية عن بُعد، وقد تشمل هذه المخاطر على سبيل المثال لا الحصر ما يلي:",
"liveCareTermsConditions19a": "(1) المعلومات المنقولة قد لا تكون كافية (مثل ضعف دقة الصور) للسماح باتخاذ القرار الطبي المناسب من قبل الممارسين الصحيين",
"liveCareTermsConditions19b": "(2) قد يقرر الممارسين الصحيين أن المعلومات المنقولة ليست ذات جودة كافية، مما يستلزم مقابلة المريض وجهاً لوجه، أو على الأقل إعادة جدولة استشارة طبية صوتية ومرئية في وقت لاحق",
"liveCareTermsConditions19c": "(3) يمكن أن يحدث تأخير في تقديم الرعاية الطبية بسبب أوجه القصور أو الأعطال في الأجهزة الإلكترونية",
"liveCareTermsConditions19d": "(4) يعتمد الممارس الصحي على المريض لتقديم جميع المعلومات ذات الصلة شفوياً وعبر الاستخدام المناسب لجهاز الكاميرا الخاص بالمريض. من الممكن ألا يتم تقديم المعلومات إلى الممارس أو قد لا تكون واضحة بقدر الوضوح الذي يتوفر للممارس الصحي أثناء إجراء الفحص البدني الشخصي",
"liveCareTermsConditions19e": "(5) قد تفشل البروتوكولات الأمنية، مما يتسبب في انتهاك خصوصية المعلومات الطبية الشخصية. على الرغم من أننا نتخذ تدابير معقولة لحماية أمن المعلومات المنقولة عبر التطبيق و/أو الموقع الإلكتروني، إلا أنه لا يمكن أبدًا ضمان أن تكون عمليات الإرسال هذه آمنة تماماً، والمعلومات المنقولة عبر شبكات الاتصالات العامة خارجة عن سيطرتنا ولا يمكننا تأمينها.",
"liveCareTermsConditions20": "3-4\tفي حال كنت تستخدم خدمات الرعاية الصحية عن بُعد ولكنك تواجه أي حالة أو مخاوف مستمرة، فإننا نوصيك بحجز استشارة شخصية.",
"liveCareTermsHeading6": "4.\tإخلاء المسؤولية عن الضمان",
"liveCareTermsConditions21": "4-1\tنحن لا نضمن ولا نقر أو نتعهد بأن خدمات الرعاية الصحية عن بُعد ستلبي احتياجاتك، أو أنها ستكون مستمرة دون انقطاع أو سيتم توفيرها في الوقت المناسب أو أنها آمنة أو خالية من الأخطاء أو خالية من الفيروسات. لن نتحمل أي مسؤولية تجاهك في حالة تعذر حدوث جلسة خدمات الرعاية الصحية عن بُعد أو تمت مقاطعتها أو التخلي عنها بسبب عدم توفر منصتنا عبر الإنترنت ومع ذلك، في حالة تعذر علينا إجراء جلسة خدمات الرعاية الصحية عن بُعد بالكامل بسبب تعطل منصتنا، فلن نتقاضى منك أي أجور عن جلسة الاستشارة البديلة.",
"liveCareTermsConditions22": "4-2\tتُستثنى جميع الضمانات الصريحة أو الضمنية بموجب النظام، باستثناء تلك التي لا يمكن استثناؤها بشكل قانوني.",
"liveCareTermsConditions23": "4-3\tلا يوجد في هذا القسم 4 أي نص يُقصد منه استبعاد أو تقييد مسؤوليتنا عن أي مطالبة تتعلق بالإهمال الطبي، إلى الحد الذي لا يسمح لنا فيه النظام المعمول به في المنطقة المسموح بها ذات الصلة بالقيام بذلك.",
"liveCareTermsHeading7": "5.\tالأهلية والتسجيل والاستخدام",
"liveCareTermsConditions24": "5-1\tمن أجل الدخول إلى خدمات الرعاية الصحية عن بُعد، يتعين عليك أن تفي بمتطلبات الحد الأدنى للسن وهو 18 عاماً (يتم احتسابها وفقاً للتقويم الميلادي) وأن تتمتع بالأهلية القانونية الكاملة.",
"liveCareTermsConditions25": "5-2\tيجب أن يحصل أي شخص يقل عمره عن 18 عاماً (يتم احتسابها وفقاً للتقويم الميلادي) يقوم بالدخول إلى خدمات الرعاية الصحية عن بُعد على إذن من أحد الوالدين أو الوصي للقيام بذلك.",
"liveCareTermsConditions26": "5-3\tأنت تضمن وتقر وتتعهد بأن لديك الحق القانوني والقدرة على الدخول إلى التطبيق و/أو موقع الويب وفقاً لهذه الشروط والأحكام و، عند الاقتضاء، إجراء الحجوزات والمدفوعات من خلال التطبيق أو موقع الويب.",
"liveCareTermsConditions27": "5-4\tتتم عملية التسجيل وفق ما ورد في شروط استخدام الخدمات الإلكترونية",
"liveCareTermsConditions28": "5-5\tخدمات الرعاية الصحية عن بُعد مخصصة فقط لاستخدامك الشخصي واستخدام أطفالك تحت إشرافك. يتعين عليك ألا تشارك بيانات تسجيل الدخول الخاصة بك (اسم المستخدم وكلمة المرور وما إلى ذلك) مع أي شخص آخر ويجب أن تتخذ خطوات معقولة للحفاظ على أمان هذه البيانات. يتم تقديم خدماتنا على أساس أن لدينا معلومات معينة حول المستخدم، والتي قد تكون مرتبطة أو تم الحصول عليها من تفاصيل حسابك (العمر والجنس والتاريخ الطبي وما إلى ذلك). نحن لا نتحمل أي مسؤولية من أي نوع تجاهك أو تجاه أي شخص آخر إذا تم تقديم خدماتنا عبر حساب مستخدم إلى شخص آخر غير المستخدم المسجل. هذا مهم بشكل خاص لخدمات الرعاية الصحية عن بعد، حيث سيحتاج الممارس الصحي إلى تقييم المعلومات التي يقدمها المريض أثناء الاستشارة في سياق الملف الطبي للمريض وتاريخه.",
"liveCareTermsConditions29": "5-6\tأنت مسؤول عن التأكد من أن لديك جهازاً متصلاً بالإنترنت وقادراً على إرسال واستقبال الفيديو والصوت. نحن لسنا مسؤولين عن أي انقطاع في جلسة خدمات الرعاية الصحية عن بعد بسبب ناجم عن جهازك أو اتصالك بالإنترنت. أنت مسؤول عن جميع التكاليف المرتبطة باتصالك بالإنترنت واستخدامك لها.",
"liveCareTermsConditions30": "5-7\tقد نقوم من وقت لآخر بإصدار تحديثات لتطبيقنا و/أو موقع الويب الخاص بنا. يجب عليك التأكد من أنك تقوم بتشغيل أحدث إصدار من التطبيق. قد يؤدي استخدام الإصدارات القديمة من التطبيق إلى حدوث أخطاء فنية أثناء استخدامك لخدمات الرعاية الصحية عن بُعد وقد يعرضك ذلك لمخاطر أمنية إضافية.",
"liveCareTermsHeading8": "6.\tخدمات الرعاية الصحية عن بُعد",
"liveCareTermsConditions31": "خدمات الرعاية الصحية عن بُعد الخاصة بنا سوف تكون لتخصصات طبية محددة أو لمواعيد المتابعة أو إعادة صرف الدواء. ",
"liveCareTermsHeading9": "7.\tالأجور والسداد",
"liveCareTermsConditions32": "خدمات الرعاية الصحية عن بُعد الخاصة بنا سوف تكون لتخصصات طبية محددة أو لمواعيد المتابعة أو إعادة صرف الدواء. ",
"liveCareTermsConditions33": "7-1\tستتم الإشارة إلى أجور استشارة خدمات الرعاية الصحية عن بُعد قبل أن تختار المتابعة. بمجرد المتابعة، فإنك توافق على سداد الأجور. لا تشمل تلك الأجور الأولية أي أجور لاحقة مثل أجور الوصفات الطبية والاختبارات المعملية واستشارات المتابعة وما إلى ذلك من أجور وسيتم إبلاغك بها بعد الاستشارة الأولية.",
"liveCareTermsConditions34": "7-2\tيتم تحديد شروط وطرق السداد المعمول بها في شروط استخدام الخدمات عبر الإنترنت الخاصة بنا.",
"liveCareTermsConditions35": "7-3\tقد يكون من الممكن فرض أجور خدمات الرعاية الصحية عن بُعد مباشرة على مزود التأمين الطبي، على الرغم من أننا لا نقدم أي تعهد أو ضمان بأن هذا سيكون ممكناً في أي وقت. تضمنت شروط استخدام الخدمات عبر الإنترنت الخاصة بنا مزيد من التفاصيل حول ذلك. إذا كان هذا التسهيل الخدمي متاحاً واخترت استخدامه، فستحتاج إلى تزويدنا بتفاصيل شركة التأمين الطبي وتغطية وثيقة التأمين، ما لم تكن قد قمت بذلك مسبقاً وربطتها بحسابك. أنت تفوضنا بموجب هذا بالتواصل مع أي شركة تأمين وفق التفاصيل التي تقدمها لنا في وقت الشراء أو المرتبطة بحسابك من أجل الحصول على الموافقة على تكلفة الخدمات المقدمة لك. أنت تقر بأنه قد يُطلب منا تقديم تفاصيل محددة عن الخدمات الطبية التي تطلبها من أجل القيام بذلك وتوافق صراحة على هذا الإفصاح.",
"liveCareTermsHeading10": "8.\tالاتصالات و/أو الاستفسارات",
"liveCareTermsConditions36": "في حال وجود أي أسئلة و/أو استفسارات بخصوص استخدام خدمات الرعاية الصحية عن بُعد، يرجى التواصل معنا على: EServices.HMG@drsulaimanalhabib.com أو الاتصال على الرقم: 9553 525 011",
"liveCareTermsHeading11": "9.\tحدود المسؤولية",
"liveCareTermsConditions37": "9-1\tمع مراعاة أي مسؤوليات ينص عليها النظام والتي لا يمكن استبعادها بموجبه، لا تتحمل المجموعة أو أي من مديرينا أو موظفينا أو وكلائنا أو مقاولينا المسؤولية تجاهك عن أي خسائر وأضرار ومسؤوليات ومطالبات ونفقات (بما في ذلك على سبيل المثال لا الحصر الأتعاب القانونية وتكاليف الدفاع أو التسوية) أيا كانت، سواء كانت مباشرة أو غير مباشرة أو تبعية، ناشئة عن أو متعلقة باستخدامك لخدمات الرعاية الصحية عن بُعد، أياً كان سبب نشوئها، سواء كان ذلك بموجب عقد أو المسؤولية التقصيرية (بما في ذلك الإهمال) أو النظام أو غير ذلك.",
"liveCareTermsConditions38": "9-2\tلن نكون مسؤولين تجاهك أو تجاه أي شخص آخر عن أي خسارة ناتجة عن سبب خارج عن سيطرتنا. يتضمن ذلك تعطل المعدات الإلكترونية أو الميكانيكية أو خطوط الاتصالات (بما في ذلك الهاتف والكابل والإنترنت)، أو الدخول غير المصرح به، أو أفعال أو إغفالات الأطراف الثالثة، أو الفيروسات، أو السرقة، أو أخطاء المشغل، أو الطقس القاسي أو غير العادي (بما في ذلك الفيضانات، أو الزلازل، أو أي من أفعال القضاء والقدر الأخرى)، أو الحرائق والحروب والتمرد والأعمال الإرهابية وأعمال شغب والنزاع العمالي أو المشاكل العمالية الأخرى أو الحوادث أو الطوارئ أو التأخير أو الحجز الزائد أو الإلغاء، أو الزوال الملكي أو أي إجراء حكومي.",
"liveCareTermsHeading12": "10.\tإنهاء الاستخدام",
"liveCareTermsConditions39": "10-1\tيجوز لنا التوقف عن توفير خدمات الرعاية الصحية عن بُعد في أي وقت ولأي سبب. لن نتحمل أي مسؤولية عن أي توقف، بما في ذلك عندما تكون خلال الاستشارات أو دورات العلاج باستخدام خدمات الرعاية الصحية عن بُعد. سنساعدك للتمكن من حجز الاستشارات الشخصية بدلاً من ذلك أو في نقل المعلومات ذات الصلة إلى مزود طبي بديل لتمكينك من مواصلة العلاج (مع مراعاة أنظمة حماية البيانات والسرية والممارسات الطبية والممارسات الطبية الجيدة السائدة).",
"liveCareTermsConditions40": "10-2\tيجوز لنا إنهاء استخدامك ودخولك إلى خدمات الرعاية الصحية عن بُعد لأي سبب أو بدون سبب، في أي وقت، سواء بإشعارك أو بدونه ودون تحمل أي مسؤولية تجاهك، ومع ذلك قد تنشأ هذه المسؤولية (على الرغم من أننا سنأخذ واجباتنا الطبية تجاهك بعين الاعتبار في حالة القيام بذلك).",
"liveCareTermsConditions41": "10-3\tستظل جميع القيود وجميع إخلاءات الطرف من المسؤولية والاستثناءات والقيود الخاصة بمسؤوليتنا سارية بعد أي إنهاء. عند الإنهاء، يتعين عليك عدم الدخول إلى أو استخدام التطبيق و/أو موقع الويب بشكل مباشر أو غير مباشر.",
"liveCareTermsHeading13": "11.\tالقانون الحاكم والاختصاص القضائي",
"liveCareTermsConditions42": "النظام المعمول به على التطبيق و/أو شروط وأحكام مستخدم موقع الويب والنزاعات الناشئة عنها أو المتعلقة بها هي أنظمة المملكة العربية السعودية. وأنت تخضع بشكل لا رجوع فيه للاختصاص القضائي الحصري لمحاكم المملكة العربية السعودية.",
"liveCareTermsHeading14": "12.\tالإقرارات",
"liveCareTermsConditions43": "12-1\tباستثناء ما هو منصوص عليه صراحةً في هذه الشروط والأحكام أو ما يسمح به أي نظام معمول به، فإنك توافق على ما يلي:",
"liveCareTermsConditions43a": "(1) أنك قد قرأت وفهمت هذه الشروط والأحكام والبنود الأخرى التي تشكل جزءاً من اتفاقيتك معنا",
"liveCareTermsConditions43b": "(2) أنك قد قرأت سياسة الخصوصية الخاصة بنا وتوافق بموجبه صراحة على استخدامنا لمعلوماتك الشخصية كما هو موضح فيها. بالإضافة إلى ذلك، أنت تدرك أن رقم الاتصال أو عنوان البريد الإلكتروني الذي زودتنا به عند التسجيل سيتم استخدامه للاتصالات من قبل المجموعة من خلال الرسائل القصيرة أو البريد الإلكتروني أو أي طريقة أخرى فيما يتعلق بخدمات الرعاية الصحية عن بُعد، بما في ذلك إشعارات المواعيد والخدمات الطبية الحالية.",
"liveCareTermsConditions43c": "(3) أنت تدرك أن لديك الحق في حجب أو سحب موافقتك بشأن استخدام التطبيق و/أو موقع الويب في سياق رعايتك في أي وقت، دون التأثير على حقك في تلقي الرعاية أو العلاج في المستقبل؛",
"liveCareTermsConditions43d": "(4) أنت تدرك أنك قد تتوقع الحصول على مزايا من استخدام التطبيق و/أو موقع الويب فيما يتعلق برعايتك الصحية، ولكن لا يمكن ضمان أي نتائج أو تأكيدها؛",
"liveCareTermsConditions43e": "(5) أنت تدرك أنه قد تتم مشاركة معلومات الرعاية الصحية الخاصة بك مع موظفين آخرين في المجموعة لأغراض جدولة المواعيد والفواتير؛",
"liveCareTermsConditions43f": "(6) أنت تدرك أن صور الفيديو والتسجيلات الصوتية الخاصة بك قد يتم التقاطها وتخزينها إلكترونياً. وأنت تدرك كذلك أنه يمكن استخدام هذه التسجيلات لاحقاً فيما يتعلق بالإجراءات النظامية إذا لزم الأمر ولكن لن يتم استخدامها لأي غرض بخلاف توفير خدمات الرعاية الصحية عن بُعد وخدمات المتابعة الطبية لك. أنت تقر وتوافق على استخدام هذه الصور والتسجيلات الصوتية من قبلنا للأغراض الموضحة؛",
"liveCareTermsConditions43g": "(7) أنك تدرك مخاطر الاتصال عبر البريد الإلكتروني والرسائل النصية، ولا سيما مخاطر الخصوصية. أنت تدرك أن المجموعة لا يمكنها ضمان أمان وسرية البريد الإلكتروني أو الاتصالات النصية. لن تكون المجموعة مسؤولة عن الرسائل التي لم يتم استلامها أو تسليمها بسبب عطل فني أو الكشف عن معلومات سرية ما لم يكن ذلك بسبب سوء سلوك متعمد.",
"liveCareTermsHeading15": "13.\tشروط وأحكام عامة",
"liveCareTermsConditions44": "13-1\tيجوز لنا في أي وقت تغيير هذه الشروط والأحكام لأسباب أمنية أو نظامية أو تنظيمية ، أو لتعكس التحديثات أو التغييرات التي تطرأ على خدمات أو وظائف خدمات الرعاية الصحية عن بُعد أو التطبيق، عن طريق نشر الشروط والأحكام المتنوعة على التطبيق. سنخطرك في التطبيق بأن هذه الشروط والأحكام قد تغيرت قبل استخدامك التالي لخدمات الرعاية الصحية عن بُعد وستتاح لك الفرصة للموافقة على الشروط المعدلة قبل استخدام الخدمة مرة أخرى.",
"liveCareTermsConditions45": "13-2\tيجوز لنا أداء أي من التزاماتنا، وممارسة أي من الحقوق الممنوحة لنا بموجب هذه الشروط والأحكام، من خلال طرف ثالث. يجوز لنا التنازل عن أي من حقوقنا والتزاماتنا أو جميعها بموجب هذه الشروط والأحكام إلى أي طرف ثالث.",
"liveCareTermsConditions46": "13-3\tإذا تبين أن أي بند أو جزء من أي بند من هذه الشروط والأحكام باطل أو غير قابل للإنفاذ أو غير صالح، فسيتم فصله عن هذه الشروط والأحكام، تاركاً باقي الشروط سارية المفعول بكامل الأثر والنفاذ، بشرط ألا يغير فصل ذلك البند من الطبيعة الأساسية لهذه الشروط والأحكام.",
"liveCareTermsHeading16": "14.\tالشكاوى",
"liveCareTermsConditions47": "خدمات الرعاية الصحية عن بُعد الخاصة بنا سوف تكون لتخصصات طبية محددة أو لمواعيد المتابعة أو إعادة صرف الدواء. ",
"liveCareTermsConditions48": "إذا كانت لديك أي شكاوى أو مخاوف بشأن التطبيق و/أو موقع الويب أو خدماتنا أو كيفية تعاملنا مع معلوماتك الشخصية، فيرجى التواصل معنا على: EServices.HMG@drsulaimanalhabib.com أو الاتصال على الرقم: 9553 525 011"
}

@ -21,7 +21,7 @@
"mySchedule": "My Schedule",
"logout": "Logout",
"respirationRate": "Respiration Rate",
"bookAppo": "New Appointment",
"bookAppo": "Book Appointment",
"searchBy": "Search By:",
"clinic": "Clinic",
"byClinic": "By Clinic",
@ -1949,14 +1949,98 @@
"invoiceHistory": "Invoice History",
"parkingQR": "Parking QR Code",
"parkingQRDesc": "Scan the below QR Code at the exit parking gate:",
"livecareUpdateInsuranceSent": "Your insurance update request is being processed. You will be notified once the update is completed.",
"livecareUpdateInsurance": "Please note that your insurance update request will be sent to the our LiveCare team for update. Would you like to proceed?",
"schedule": "Schedule",
"doctorSchedule": "Doctor Schedule",
"weeklyShift": "Weekly Shift",
"dayOff": "Off",
"notAvailable": "Not Available",
"hide": "Hide"
"livecareUpdateInsuranceSent": "Your insurance update request is being processed. You will be notified once the update is completed.",
"livecareUpdateInsurance": "Please note that your insurance update request will be sent to the our LiveCare team for update. Would you like to proceed?",
"schedule": "Schedule",
"doctorSchedule": "Doctor Schedule",
"weeklyShift": "Weekly Shift",
"dayOff": "Off",
"notAvailable": "Not Available",
"hide": "Hide",
"viewPatientQR": "View Patient QR Code",
"patientQR": "Patient QR Code",
"forText": "For",
"liveCareTermsHeading": "TELEHEALTH SERVICES TERMS OF USE",
"liveCareTermsIntroHeading": "INTRODUCTION",
"liveCareTermsConditions1": "Dr. Sulaiman Al Habib Medical Services Group Company (the “Group”) is one of the leaders in the healthcare sector in the Kingdom of Saudi Arabia with an aim to always improve its services in the region, and as a step to take our services to another level and to facilitate the process of receiving healthcare services we have adopted the approach of providing some of our services remotely to patients. These terms apply to our Telehealth Services, which enable consultations to occur between a doctor and a patient over the internet, using audio or and video conferencing technology (often referred to as “Telehealth Services”).",
"liveCareTermsConditions2": "The purpose of the Telehealth Services is to facilitate communication between healthcare practitioners and patients to provide healthcare services through advanced electronic communication means, which allows healthcare professionals to evaluate, diagnose, treat and follow-up with patients remotely without the need for patients to attend in person.).",
"liveCareTermsHeading2": "AGREEMENT",
"liveCareTermsConditions3": "Telehealth Services is delivered over the Group’s mobile application or and Website. The following terms apply to the Telehealth Services: ",
"liveCareTermsConditions3a": " - Website Terms of Use",
"liveCareTermsConditions3b": " - App User Agreement",
"liveCareTermsConditions3c": " - Online Services Terms of Use",
"liveCareTermsConditions3d": " - These Telehealth Services User Terms of Use",
"liveCareTermsConditions4": "By agreeing to proceed with the Telehealth Services and accepting these terms, you enter into an agreement with The Group incorporating all the above terms (Telehealth Services Agreement).",
"liveCareTermsConditions5": "Our Privacy Policy should also be read. It contains details about how we use your personal information and your rights. By proceeding with the Telehealth Services, you agree to our use of your personal information in accordance with our Privacy Policy and you expressly consent to our use of your personal information for the performance of the Telehealth Services and the discharge of our professional duties as a medical care provider.",
"liveCareTermsConditions6": "Our Privacy Policy should also be read. It contains details about how we use your personal information and your rights. By proceeding with the Telehealth Services, you agree to our use of your personal information in accordance with our Privacy Policy and you expressly consent to our use of your personal information for the performance of the Telehealth Services and the discharge of our professional duties as a medical care provider.",
"liveCareTermsHeading3": "1. DEFINITIONS",
"liveCareTermsConditions7": "Group means Dr. Sulaiman Al Habib Medical Services Group Company and its subsidiaries. ",
"liveCareTermsConditions8": "Telehealth Services means the Group’s telehealth service, enabling remote consultation, diagnosis and follow-up between the patient and doctor, using the Application and or the Website.",
"liveCareTermsConditions9": "Our Services means certain services provided by the Group which covers viewing patients’ medical records and medical test results, providing medical diagnostics in addition to the availability of voice and video Telehealth Service between healthcare practitioners and patients.",
"liveCareTermsConditions10": "Application means Dr. ALHABIB Application which is a platform where Our Services are provided through and accessible by users. ",
"liveCareTermsConditions11": "Website means the Group internet website which is a platform where Our Services are provided through and accessible by users. ",
"liveCareTermsConditions12": "Terms & Conditions means these Telehealth Services Terms of Use. ",
"liveCareTermsConditions13": "Content means any message, data, information, text, sound, photos, video, graphics, code or any other material contained on the Application and or the Website or transmitted via the Application and or the Website. ",
"liveCareTermsHeading4": "2. USER UNDERTAKINGS",
"liveCareTermsConditions14": "2.1 By accessing and using Telehealth Services, you agree to these Terms & Conditions, and you should not attempt to access Our Services if you do not agree. ",
"liveCareTermsConditions15": "2.2 You warrant, represent and undertake that all information you provide about yourself and about anyone else is true and accurate. ",
"liveCareTermsConditions16": "2.3 Telehealth Services is intended for use by patients physically located in the Kingdom Saudi Arabia (Permitted Territory) and should not be used by patients outside the Permitted Territory. By agreeing to these Terms & Conditions and accessing Telehealth Services via the Application and or the Website, you represent and warrant to us that you are physically located in one of the Permitted Territory. ",
"liveCareTermsHeading5": "3. LIMITATIONS OF TELEHEALTH SERVICES AND ASSOCIATED RISKS",
"liveCareTermsConditions17": "3.1 Do not use the Application and or the Website or the Telehealth Services to communicate or treat medical emergencies. Neither the Application nor the Website nor Telehealth Services are intended for the provision of care in an emergency. IF THERE IS AN URGENT MEDICAL EMERGENCY OR A RESPONSE IS NEEDED QUICKLY REGARDING YOUR HEALTH, GO TO A NEARBY EMERGENCY CENTER/HOSPITAL OR CALL IMMEDIATELY ANY EMERGENCY PHONE NUMBER. ",
"liveCareTermsConditions18": "3.2 Telehealth Services is not appropriate for emergency conditions and there may be some treatments or medicines which cannot be provided without follow-up physical consultation. If you believe you have a serious medical condition, you should seek an in-person consultation with a licensed medical professional.",
"liveCareTermsConditions19": "3.3 Telehealth Services is intended to provide benefits including patient convenience. As such, it is most suitable as an alternative to in-person consultation for routine medical matters. However, there are potential risks associated with the use of any telehealth service, including Telehealth Services, and these risks may include but not be limited to the following: ",
"liveCareTermsConditions19a": "(a) information transmitted may not be sufficient (e.g. poor resolution of images) to allow for appropriate medical decision making by healthcare practitioners.",
"liveCareTermsConditions19b": "(b) healthcare practitioners may determine that the transmitted information is of inadequate quality, thus necessitating a face-to-face meeting with the patient, or at least a rescheduled voice and video medical consult. ",
"liveCareTermsConditions19c": "(c) delays in medical care could occur due to deficiencies or failures of the electronic devices. ",
"liveCareTermsConditions19d": "(d) the practitioner is dependent on the patient to provide all relevant information orally and via the appropriate use of the patient’s own camera device. It is possible that information may not be provided to the practitioner or may not be apparent which would have been apparent to the practitioner during an in-person physical examination. ",
"liveCareTermsConditions19e": "(e) security protocols could fail, causing a breach of privacy of personal medical information. Whilst we take reasonable measures to protect the security of the information transmitted over the Application and or the Website, such transmissions can never be guaranteed to be completely secure and information transmitted over public telecommunications networks is outside our control and cannot be secured by us.",
"liveCareTermsConditions20": "3.4 If you use Telehealth Services but are experiencing any ongoing condition or concerns, we recommend you schedule an in-person consultation. ",
"liveCareTermsHeading6": "4. DISCLAIMER OF WARRANTY ",
"liveCareTermsConditions21": "4.1 We do not warrant, represent or undertake that Telehealth Services will meet your needs, or that it will be uninterrupted, timely, secure, error-free or free from viruses. We shall have no liability to you if a Telehealth Services session cannot occur or is interrupted or abandoned due to any unavailability of our online platform, however, where the Telehealth Services session cannot be delivered fully due to the downtime of our own platform, we will not charge you for a replacement consultation.",
"liveCareTermsConditions22": "4.2 All warranties expressed or implied by law, except those that cannot be lawfully excluded, are excluded. ",
"liveCareTermsConditions23": "4.3 Nothing in this section 4 is intended to exclude or limit our liability for any medical negligence claim, to the extent the applicable law in the relevant Permitted Territory does not permit us to do so.",
"liveCareTermsHeading7": "5. ELIGIBILITY, REGISTRATION AND USE",
"liveCareTermsConditions24": "5.1 To access Telehealth Services, you must satisfy the minimum age requirement of 18 years (measured according to the Gregorian calendar) and have full legal capacity. ",
"liveCareTermsConditions25": "5.2 Any person under the age of 18 years (measured according to the Gregorian calendar) accessing Telehealth Services must have permission from their parent or guardian to do so.",
"liveCareTermsConditions26": "5.3 You warrant, represent and undertake that you possess the legal right and ability to access the Application and or the Website in accordance with these Terms & Conditions and, where relevant, to make bookings and payments through the Application and or the Website.",
"liveCareTermsConditions27": "5.4 The registration process is as provided in the Online Services Terms of Use.",
"liveCareTermsConditions28": "5.5 Telehealth Services is intended only for your own personal use and the use of your children under your supervision. You must not share your user access details (log-in name, password etc.) with any other person and must take reasonable steps to maintain the security of such details. Our services are delivered on the basis that we have certain information about the user, which may be connected to or obtained from your account details (age, gender, medical history etc.). We accept no responsibility whatsoever to you or any other person if our services are delivered via a user account to a person other than the registered user. This is particularly important for telehealth services, where the practitioner will need to assess the information, the patient provides during the consultation in the context of the patient’s medical file and history.",
"liveCareTermsConditions29": "5.6 You are responsible for ensuring you have a device connected to the internet and capable of transmitting and receiving video and sound. We are not responsible for any failure of a Telehealth Services session caused by your device or your internet connection. You are responsible for all costs associated with your own internet connection and use.",
"liveCareTermsConditions30": "5.7 From time to time we may release updates to our Application and or the Website. You should ensure that you are running the latest version of the Application. Use of old versions of the Application may lead to technical errors in your use of the Telehealth Services and may expose you to additional security risks.",
"liveCareTermsHeading8": "6. TELEHEALTH SERVICES",
"liveCareTermsConditions31": "Our Telehealth Services will be for specific medical specialties or follow-up or medication refill appointments.",
"liveCareTermsHeading9": "7. CHARGES AND PAYMENT",
"liveCareTermsConditions32": "Our Telehealth Services will be for specific medical specialties or follow-up or medication refill appointments.",
"liveCareTermsConditions33": "7.1 The charges for a Telehealth Services consultation will be indicated before you choose to proceed. By proceeding, you agree to pay the charges. Any subsequent charges, such as prescription charges, lab tests, follow-up consultations etc. are excluded from the initial charge and will be communicated to you following the initial consultation. ",
"liveCareTermsConditions34": "7.2 The applicable payment terms and methods are set out in our Online Services Terms of Use. ",
"liveCareTermsConditions35": "7.3 It may be possible to charge Telehealth Services directly to a provider of medical insurance, although we give no undertaking or warranty that this shall be possible at any given time. Further details are set out in our Online Services Terms of Use. If such facility is made available and you choose to use it, you will need to provide us with details of your medical insurer and policy coverage, unless you have already done so and linked them to your account. You hereby authorise us to contact any such insurer with the details you provide at the time of purchase, or which are linked to your account to seek approval of the cost of your services. You acknowledge that we may be required to provide specific details of the medical services sought by you to do so and you expressly consent to such disclosure.",
"liveCareTermsHeading10": "8. COMMUNICATION AND/OR INQUIRIES",
"liveCareTermsConditions36": "In case there are any questions and/or inquiries regarding the use of Telehealth Services please contact: EServices.HMG@drsulaimanalhabib.com or call 011 525 9553",
"liveCareTermsHeading11": "9. LIMITATION OF LIABILITY ",
"liveCareTermsConditions37": "9.1 Subject to any responsibilities implied by law and which cannot be excluded, neither the Group nor any of our directors, employees, agents or contractors, are liable to you for any losses, damages, liabilities, claims and expenses (including without limitation but not limited to legal costs and defense or settlement costs) whatsoever, whether direct, indirect or consequential, arising out of or referable to your use of Telehealth Services , however caused, whether in contract, tort (including negligence), statute or otherwise. ",
"liveCareTermsConditions38": "9.2 We will not be liable to you or anyone else for any loss resulting from a cause over which we do not have control. This includes failure of electronic or mechanical equipment or communications lines (including telephone, cable and Internet), unauthorized access, acts or omissions of third parties, viruses, theft, operator errors, severe or extraordinary weather (including flood, earthquake, or other act of God), fire, war, insurrection, terrorist act, riot, labor dispute or other labor problems, accident, emergency, delay, overbooking, cancellation, Royal demise or action of government",
"liveCareTermsHeading12": "10. TERMINATION OF USE",
"liveCareTermsConditions39": "10.1 We may discontinue the provision of Telehealth Services at any time and for any reason. We shall have no liability for any discontinuation, including where you are part-way through a series of consultations or courses of treatment using Telehealth Services. We will assist in enabling you to book in-person consultations instead or in transferring relevant information to an alternative medical provider to enable you to continue treatment (subject to data protection, confidentiality and medical practice laws and to prevailing medical good practice).",
"liveCareTermsConditions40": "10.2 We may terminate your use and access to Telehealth Services for any or no reason, at any time, with or without notice to you and without incurring any liability to you, however that liability may arise (although we will consider our medical duties to you, if doing so). ",
"liveCareTermsConditions41": "10.3 All restrictions and all disclaimers and exclusions and limitations of our liability will survive any termination. Upon termination, you must not directly or indirectly access or use the Application and or the Website.",
"liveCareTermsHeading13": "11. GOVERNING LAW AND JURISDICTION ",
"liveCareTermsConditions42": "The law applicable to the Application and or the Website User Terms and Conditions and to disputes arising out of or in connection with them are the laws of The Kingdom of Saudi Arabia. You irrevocably submit to the exclusive jurisdiction of the courts of the Kingdom of Saudi Arabia. ",
"liveCareTermsHeading14": "12. ACKNOWLEDGEMENTS",
"liveCareTermsConditions43": "12.1 Except as expressly set out in these Terms & Conditions or as permitted by any applicable law, you agree on the following: ",
"liveCareTermsConditions43a": "(a) you have read and understand these Terms & Conditions and the other terms forming part of your Agreement with us; ",
"liveCareTermsConditions43b": "(b) you have read our Privacy Policy and hereby expressly consent to our use of your personal information as described. In addition, you understand that the contact number or email address that you have provided on registration will be used for communications by the Group through SMS, email or any other method in connection with the Telehealth Services, including for appointment notifications and current medical services;",
"liveCareTermsConditions43c": "(c) you understand that you have the right to withhold or withdraw your consent to the use of the Application and or the Website in the course of your care at any time, without affecting your right to future care or treatment; ",
"liveCareTermsConditions43d": "(d) you understand that you may expect to receive benefits from the use of Application and or the Website concerning your healthcare, but that no results can be guaranteed or assured; ",
"liveCareTermsConditions43e": "(e) you understand that your healthcare information may be shared with other employees in the Group for scheduling and billing purposes;",
"liveCareTermsConditions43f": "(f) you understand that video images and audio recordings of you may be captured and stored electronically. You understand that these recordings may be later used in connection with legal proceedings if necessary but will not otherwise be used for any purpose other than providing Telehealth Services and follow-up medical services to you. You acknowledge and consent to the use of these images and audio recordings by us for the purposes described; ",
"liveCareTermsConditions43g": "(g) you understand the risks of communicating by email and text messages, particularly the privacy risks. You understand that the Group cannot guarantee the security and confidentiality of email or text communication. The Group will not be responsible for messages that are not received or delivered due to technical failure, or for disclosure of confidential information unless caused by intentional misconduct.",
"liveCareTermsHeading15": "13. GENERAL TERMS AND CONDITIONS ",
"liveCareTermsConditions44": "13.1 We may at any time vary these Terms & Conditions for security, legal or regulatory reasons, or to reflect updates or changes to the services or functionality of Telehealth Services or the Application, by publishing the varied Terms & Conditions on the Application. We will notify you on the Application that these Terms & Conditions have changed before your next use of the Telehealth Services and you will have the opportunity to agree to the revised terms before using the service again. ",
"liveCareTermsConditions45": "13.2 We may perform any of our obligations, and exercise any of the rights granted to us under these Terms & Conditions, through a third party. We may assign any or all our rights and obligations under these Terms & Conditions to any third party.",
"liveCareTermsConditions46": "13.3 If any clause or part of any clause of these Terms & Conditions is found to be void, unenforceable or invalid, then it will be severed from these Terms & Conditions, leaving the remainder in full force and effect, provided that the severance has not altered the basic nature of these Terms & Conditions. ",
"liveCareTermsHeading16": "14. COMPLAINTS",
"liveCareTermsConditions47": "Our Telehealth Services will be for specific medical specialties or follow-up or medication refill appointments.",
"liveCareTermsConditions48": "If you have any complaints or concerns about the Application and or the Website, Our Services, or how we handle your personal information please contact us on: EServices.HMG@drsulaimanalhabib.com or call 011 525 9553"
}

@ -20,7 +20,5 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict>
</plist>

@ -8,8 +8,8 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
1587365701FF0D2AAAFED0D8 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A83C76CFF9D3F92EDE3B22B4 /* Pods_Runner.framework */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
37D27FAF2C38AC83B852680C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A83C76CFF9D3F92EDE3B22B4 /* Pods_Runner.framework */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
478CFA942E638C8E0064F3D7 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */; };
61243B492EC5FA3700D46FA0 /* PenguinModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B422EC5FA3700D46FA0 /* PenguinModel.swift */; };
@ -29,6 +29,7 @@
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -90,6 +91,7 @@
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A83C76CFF9D3F92EDE3B22B4 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -97,10 +99,11 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
76B1D2512F91BF8000DBBD1B /* Penguin.xcframework in Frameworks */,
76B1D2552F91BF8000DBBD1B /* PenNavUI.xcframework in Frameworks */,
76B1D2532F91BF8000DBBD1B /* PenguinINRenderer.xcframework in Frameworks */,
DE1EF78253E64BE86845D2CC /* Pods_Runner.framework in Frameworks */,
37D27FAF2C38AC83B852680C /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -159,6 +162,7 @@
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
@ -230,6 +234,9 @@
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
packageProductDependencies = (
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
);
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
@ -256,6 +263,9 @@
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
);
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
@ -519,7 +529,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 3A359E86ZF;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -529,7 +539,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.0.5;
MARKETING_VERSION = 0.0.12;
PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -706,7 +716,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 3A359E86ZF;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -716,7 +726,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.0.5;
MARKETING_VERSION = 0.0.12;
PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -736,7 +746,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 3A359E86ZF;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -746,7 +756,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.0.5;
MARKETING_VERSION = 0.0.12;
PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -790,6 +800,18 @@
defaultConfigurationName = Debug;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
isa = XCSwiftPackageProductDependency;
productName = FlutterGeneratedPluginSwiftPackage;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

@ -5,6 +5,24 @@
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Prepare Flutter Framework Script"
scriptText = "/bin/sh &quot;$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh&quot; prepare&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"

@ -86,7 +86,7 @@
<key>NSMicrophoneUsageDescription</key>
<string>This app requires microphone access to enable virtual consultation between patient &amp; doctor</string>
<key>NSMotionUsageDescription</key>
<string>This app requires motion detection access to function properly.</string>
<string>This app requires access to motion detection to count your daily steps.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>This app requires photo library access to select image as document &amp; upload it.</string>
<key>NSPhotoLibraryAddUsageDescription</key>

@ -138,8 +138,7 @@ 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')) {
@ -210,8 +209,8 @@ class ApiClientImp implements ApiClient {
body['TokenID'] = "@dm!n";
}
// body['TokenID'] = "@dm!n";
// body['PatientID'] = 2122445;
body['TokenID'] = "@dm!n";
// body['PatientID'] = 7000792;
// body['PatientID'] = 53320;
// body['PatientTypeID'] = 1;
// body['PatientOutSA'] = 0;
@ -271,11 +270,11 @@ class ApiClientImp implements ApiClient {
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
Clarity.setCustomTag('api_err_method', 'POST');
if (_appState.isAuthenticated) {
Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
}
Clarity.sendCustomEvent('api_socket_exception');
return;
@ -283,19 +282,19 @@ class ApiClientImp implements ApiClient {
final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr();
onFailure(message, -1, failureType: ConnectivityFailure(message));
_analytics.errorTracking.log(endPoint, error: "ClientException: $e");
// Enhanced Clarity logging
Clarity.setCustomTag('api_err_type', 'client_exception');
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
Clarity.setCustomTag('api_err_method', 'POST');
if (_appState.isAuthenticated) {
Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
}
Clarity.sendCustomEvent('api_client_exception');
return;
} on TimeoutException catch (_) {
final message = LocaleKeys.networkTimeout.tr();
@ -307,36 +306,36 @@ class ApiClientImp implements ApiClient {
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
Clarity.setCustomTag('api_err_method', 'POST');
if (_appState.isAuthenticated) {
Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
}
Clarity.sendCustomEvent('api_timeout');
return;
} catch (e) {
final message = LocaleKeys.networkUnknownError.tr();
onFailure(message, -1, failureType: ConnectivityFailure(message));
_analytics.errorTracking.log(endPoint, error: "UnknownException: $e");
// Enhanced Clarity logging for unknown errors
Clarity.setCustomTag('api_err_type', 'unknown_exception');
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
Clarity.setCustomTag('api_err_method', 'POST');
// Truncate exception message for Clarity
final exceptionMsg = e.toString();
final truncatedMsg = exceptionMsg.length > 252 ? '...${exceptionMsg.substring(exceptionMsg.length - 252)}' : exceptionMsg;
Clarity.setCustomTag('api_err_exception', truncatedMsg);
if (_appState.isAuthenticated) {
Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
}
Clarity.sendCustomEvent('api_unknown_exception');
return;
}
@ -384,24 +383,19 @@ class ApiClientImp implements ApiClient {
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'],
@ -411,8 +405,7 @@ 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(
@ -424,8 +417,7 @@ class ApiClientImp implements ApiClient {
// }
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) {
@ -459,8 +451,7 @@ 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(
@ -537,74 +528,74 @@ class ApiClientImp implements ApiClient {
final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr();
onFailure(message, -1);
_analytics.errorTracking.log(endPoint, error: "SocketException: $e");
// Clarity logging for GET requests
Clarity.setCustomTag('api_err_type', 'socket_exception');
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
Clarity.setCustomTag('api_err_method', 'GET');
if (_appState.isAuthenticated) {
Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
}
Clarity.sendCustomEvent('api_get_socket_exception');
return;
} on http.ClientException catch (e) {
final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr();
onFailure(message, -1);
_analytics.errorTracking.log(endPoint, error: "ClientException: $e");
Clarity.setCustomTag('api_err_type', 'client_exception');
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
Clarity.setCustomTag('api_err_method', 'GET');
if (_appState.isAuthenticated) {
Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
}
Clarity.sendCustomEvent('api_get_client_exception');
return;
} on TimeoutException catch (_) {
final message = LocaleKeys.networkTimeout.tr();
onFailure(message, -1);
_analytics.errorTracking.log(endPoint, error: "TimeoutException");
Clarity.setCustomTag('api_err_type', 'timeout');
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
Clarity.setCustomTag('api_err_method', 'GET');
if (_appState.isAuthenticated) {
Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
}
Clarity.sendCustomEvent('api_get_timeout');
return;
} catch (e) {
final message = LocaleKeys.networkUnknownError.tr();
onFailure(message, -1);
_analytics.errorTracking.log(endPoint, error: "UnknownException: $e");
Clarity.setCustomTag('api_err_type', 'unknown_exception');
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
Clarity.setCustomTag('api_err_method', 'GET');
final exceptionMsg = e.toString();
final truncatedMsg = exceptionMsg.length > 252 ? '...${exceptionMsg.substring(exceptionMsg.length - 252)}' : exceptionMsg;
Clarity.setCustomTag('api_err_exception', truncatedMsg);
if (_appState.isAuthenticated) {
Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
}
Clarity.sendCustomEvent('api_get_unknown_exception');
return;
}
@ -614,18 +605,18 @@ class ApiClientImp implements ApiClient {
if (statusCode < 200 || statusCode >= 400) {
onFailure(LocaleKeys.networkErrorWhileFetching.tr(), statusCode);
logApiEndpointError(endPoint, LocaleKeys.networkErrorWhileFetching.tr(), statusCode);
// Clarity logging for GET status code errors
Clarity.setCustomTag('api_err_type', 'status_code_error');
Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim()));
Clarity.setCustomTag('api_err_endpoint', _getEndpointName(endPoint));
Clarity.setCustomTag('api_err_method', 'GET');
Clarity.setCustomTag('api_err_status', statusCode.toString());
if (_appState.isAuthenticated) {
Clarity.setCustomTag('api_err_user_id', _appState.getAuthenticatedUser()!.patientId.toString());
}
Clarity.sendCustomEvent('api_get_status_error');
} else {
var parsed = json.decode(utf8.decode(response.bodyBytes));
@ -813,7 +804,7 @@ class ApiClientImp implements ApiClient {
// Extract the last part of the endpoint path for cleaner tracking
const int clarityLimit = 100;
final cleanEndpoint = endpoint.split('/').last;
if (cleanEndpoint.length <= clarityLimit) return cleanEndpoint;
return '...${cleanEndpoint.substring(cleanEndpoint.length - (clarityLimit - 3))}';
}

@ -1,11 +1,12 @@
import 'package:amazon_payfort/amazon_payfort.dart';
import 'package:flutter_amazonpaymentservices/environment_type.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:myfatoorah_flutter/myfatoorah_flutter.dart';
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
@ -44,12 +45,17 @@ class ApiConsts {
// var payFortEnvironment = FortEnvironment.test;
// var applePayMerchantId = "merchant.com.hmgwebservices.uat";
static var myFatoorahEnvironmentType = MFEnvironment.TEST;
static var myFatoorahCountry = MFCountry.SAUDIARABIA;
static setBackendURLs() {
switch (appEnvironmentType) {
case AppEnvironmentTypeEnum.prod:
baseUrl = "https://hmgwebservices.com/";
payFortEnvironment = FortEnvironment.production;
payFortEnvironmentType = EnvironmentType.production;
myFatoorahEnvironmentType = MFEnvironment.LIVE;
myFatoorahCountry = MFCountry.SAUDIARABIA;
applePayMerchantId = "merchant.com.hmgwebservices";
SERVICE_URL = "https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx";
TAMARA_URL = "https://mdlaboratories.com/tamaralive/Home/Checkout";
@ -66,6 +72,8 @@ class ApiConsts {
baseUrl = "https://uat.hmgwebservices.com/";
payFortEnvironment = FortEnvironment.test;
payFortEnvironmentType = EnvironmentType.sandbox;
myFatoorahEnvironmentType = MFEnvironment.TEST;
myFatoorahCountry = MFCountry.KUWAIT;
applePayMerchantId = "merchant.com.hmgwebservices.uat";
SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx';
TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout";
@ -77,12 +85,13 @@ class ApiConsts {
symptomsCheckerApi = symptomsCheckerApiUAT;
symptomsCheckerUsername = 'guest_user';
symptomsCheckerPassword = '123456';
break;
case AppEnvironmentTypeEnum.uat:
baseUrl = "https://uat.hmgwebservices.com/";
payFortEnvironment = FortEnvironment.test;
payFortEnvironmentType = EnvironmentType.sandbox;
myFatoorahEnvironmentType = MFEnvironment.TEST;
myFatoorahCountry = MFCountry.KUWAIT;
applePayMerchantId = "merchant.com.hmgwebservices.uat";
SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx';
TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout";
@ -100,6 +109,8 @@ class ApiConsts {
baseUrl = "https://webservices.hmg.com/";
payFortEnvironment = FortEnvironment.production;
payFortEnvironmentType = EnvironmentType.production;
myFatoorahEnvironmentType = MFEnvironment.LIVE;
myFatoorahCountry = MFCountry.SAUDIARABIA;
applePayMerchantId = "merchant.com.hmgwebservices";
SERVICE_URL = "https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx";
TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout";
@ -117,6 +128,8 @@ class ApiConsts {
baseUrl = "https://uat.hmgwebservices.com/";
payFortEnvironment = FortEnvironment.test;
payFortEnvironmentType = EnvironmentType.sandbox;
myFatoorahEnvironmentType = MFEnvironment.TEST;
myFatoorahCountry = MFCountry.KUWAIT;
applePayMerchantId = "merchant.com.hmgwebservices.uat";
SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx';
TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout";
@ -134,6 +147,8 @@ class ApiConsts {
baseUrl = "https://uat.hmgwebservices.com/";
payFortEnvironment = FortEnvironment.test;
payFortEnvironmentType = EnvironmentType.sandbox;
myFatoorahEnvironmentType = MFEnvironment.TEST;
myFatoorahCountry = MFCountry.KUWAIT;
applePayMerchantId = "merchant.com.hmgwebservices.uat";
SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx';
TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout";
@ -301,7 +316,7 @@ class ApiConsts {
static String googleCloudStorageENTranslationFileBaseURL = "https://storage.googleapis.com/hmg-patientapp-translations";
// ************ static values for Api ****************
static final double appVersionID = 21.6;
static final double appVersionID = 22.1;
// static final double appVersionID = 50.7;
static final int appChannelId = 3;
@ -870,8 +885,7 @@ 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=';
@ -917,8 +931,6 @@ var AUTO_GENERATE_INVOICE_TAMARA = 'Services/PayFort_Serv.svc/REST/Tamara_Getinf
var GET_ONESIGNAL_VOIP_TOKEN = 'https://onesignal.com/api/v1/players';
var CANCEL_PHARMA_LIVECARE_REQUEST = 'https://vcallapi.hmg.com/api/PharmaLiveCare/SendPaymentStatus';
var INSERT_FREE_SLOTS_LOGS = 'Services/Doctors.svc/Rest/InsertDoctorFreeSlotsLogs';
var GET_NATIONALITY = 'Services/Lists.svc/REST/GetNationality';

@ -110,6 +110,7 @@ class AppAssets {
static const String report_icon = '$svgBasePath/report_icon.svg';
static const String radiology_icon = '$svgBasePath/radiology_icon.svg';
static const String prescription_item_icon = '$svgBasePath/prescription_item_icon.svg';
static const String vital_sign_item_icon = '$svgBasePath/vital_sign_item_icon.svg';
static const String checkin_location_icon = '$svgBasePath/checkin_location_icon.svg';
static const String checkin_nfc_icon = '$svgBasePath/checkin_nfc_icon.svg';
static const String checkin_qr_icon = '$svgBasePath/checkin_qr_icon.svg';
@ -399,6 +400,8 @@ class AppAssets {
static const String habibLogoOnly = '$svgBasePath/habib logo_only.svg';
static const String descriptionIcon = '$svgBasePath/description_icon.svg';
static const String selectImageIcon = '$svgBasePath/select_image_icon.svg';
static const String patientQRCodeIcon = '$svgBasePath/patient_qr_code_icon.svg';
static const String noImageFound = '$svgBasePath/no_image_found.svg';
// PNGS //
static const String hmgLogo = '$pngBasePath/hmg_logo.png';

@ -48,12 +48,19 @@ class AppState {
bool isPaytabsEnabled = false;
bool isPayfortHostedPageEnabled = true;
bool isMyFatoorahEnabled = false;
// bool isMyFatoorahEnabled = true;
void setIsPaytabsEnabled(bool value) {
isPaytabsEnabled = value;
// isPaytabsEnabled = true;
}
void setIsMyFatoorahEnabled(bool value) {
// isMyFatoorahEnabled = value;
isMyFatoorahEnabled = true;
}
void setIsPayfortHostedPageEnabled(bool value) {
isPayfortHostedPageEnabled = value;
// isPayfortHostedPageEnabled = false;
@ -170,7 +177,6 @@ class AppState {
set setIsAuthenticated(v) => isAuthenticated = v;
String deviceTypeID = "";
set setDeviceTypeID(v) => deviceTypeID = v;
@ -179,6 +185,14 @@ class AppState {
String get getFamilyFileTokenID => _familyFileTokenID;
bool isSafeDevice = true;
// set setIsSafeDevice(v) => isSafeDevice = v;
set setIsSafeDevice(bool value) {
isSafeDevice = value;
}
set setFamilyFileTokenID(String value) {
_familyFileTokenID = value;
}

@ -44,6 +44,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_rep
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_repo.dart';
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart';
import 'package:hmg_patient_app_new/features/myfatoorah/myfatoorah_view_model.dart';
import 'package:hmg_patient_app_new/features/notifications/notifications_repo.dart';
import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart';
import 'package:hmg_patient_app_new/features/offers_and_discounts/offers_and_discounts_repo.dart';
@ -84,6 +85,9 @@ 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/services/notification_service.dart';
import 'package:hmg_patient_app_new/services/permission_service.dart';
import 'package:hmg_patient_app_new/services/remote_config_service.dart';
import 'package:hmg_patient_app_new/services/security_service.dart';
import 'package:hmg_patient_app_new/features/occasion_theme/occasion_theme_view_model.dart';
import 'package:hmg_patient_app_new/core/services/turnstile_service.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart';
@ -157,6 +161,10 @@ class AppDependencies {
getIt.registerLazySingleton<PermissionService>(() => PermissionService());
getIt.registerLazySingleton<TurnstileService>(() => TurnstileService(getIt<LoggerService>()));
getIt.registerLazySingleton<SecurityService>(() => SecurityServiceImpl(
appState: getIt(),
loggerService: getIt(),
));
// Repositories
getIt.registerLazySingleton<CommonRepo>(() => CommonRepoImp(loggerService: getIt()));
@ -369,10 +377,28 @@ class AppDependencies {
errorHandlerService: getIt(),
));
getIt.registerLazySingleton<PayTabsViewModel>(() => PayTabsViewModel(appState: getIt()));
getIt.registerLazySingleton<PayTabsViewModel>(() => PayTabsViewModel(
appState: getIt(),
payfortRepo: getIt(),
errorHandlerService: getIt(),
));
getIt.registerLazySingleton<MyFatoorahViewModel>(() => MyFatoorahViewModel(
appState: getIt(),
errorHandlerService: getIt(),
payfortRepo: getIt(),
));
getIt.registerLazySingleton<ClarityService>(
() => ClarityService(appState: getIt.get<AppState>()),
);
// Remote-config-driven "occasion" theming (National Day, Eid, etc.).
getIt.registerLazySingleton<RemoteConfigService>(() => RemoteConfigServiceImp(loggerService: getIt()));
getIt.registerLazySingleton<OccasionThemeViewModel>(() => OccasionThemeViewModel(
remoteConfigService: getIt(),
cacheService: getIt(),
loggerService: getIt(),
));
}
}

@ -0,0 +1,5 @@
class MyFatoorahConfig {
static const String configAPIKey = "SK_KWT_0auB76h3Frhj7aUBSZa6H5ei792eZoGAPh0bBxKsrgtyjqKgf38MPnZqzGqagErb";
}

@ -3,6 +3,11 @@ class PaymentSdkDefaultConfig {
static const String profileID = "119730";
static const String serverKey = "SWJ9LD22MT-JLJZLBKTLK-WGWTLWGTWR";
static const String clientKey = "C7K2DQ-PDDN6B-69B2K7-P9GB9T";
// static const String profileID = "135577";
// static const String serverKey = "SDJ9LD22LR-J9LRBKZDRD-ZRZKWDWBRR";
// static const String clientKey = "CBK2DQ-PDPN6P-BR2K9D-VPBTB2";
static const String defaultCurrency = "SAR";
static const double defaultAmount = 10.0;
static const String defaultMerchantCountryCode = "SA";

@ -18,6 +18,10 @@ class TurnstileService {
// Initialize an instance of invisible Cloudflare Turnstile with your site key
final turnstile = CloudflareTurnstile.invisible(
siteKey: _siteKey,
onTimeout: () {},
options: TurnstileOptions(
retryInterval: const Duration(milliseconds: 2000),
),
);
try {

@ -0,0 +1,15 @@
import 'package:freerasp/freerasp.dart';
final talsecConfig = TalsecConfig(
androidConfig: AndroidConfig(
packageName: 'com.cloudsolutions.HMGPatientApp',
signingCertHashes: ['6tvWaoN5coG4SnfxGbdQlcLmM0J4ePQwDjrKIg+QkV0=', 'j6VEqVhrypHMIiXiFdRLDdGwjGaMGWY7KAdBJA+Z4Pc='], // Must be the release cert hash
supportedStores: ['com.android.vending'], // Google Play Store
),
iosConfig: IOSConfig(
bundleIds: ['com.cloudsolutions.HMGPatientApp'], // iOS Bundle ID
teamId: '3A359E86ZF', // Found in Apple Developer portal
),
watcherMail: '', // Required to receive security alerts
isProd: true, // Enforces strict checks for release builds
);

@ -28,21 +28,21 @@ class DateUtil {
const end = "+0300)";
final startIndex = date.indexOf(start);
final endIndex = date.indexOf(end, startIndex + start.length);
// if (projectId == 2 || projectId == 3) {
// return DateTime.fromMillisecondsSinceEpoch(
// int.parse(
// date.substring(startIndex + start.length, endIndex),
// ),
// isUtc: true)
// .add(Duration(hours: 4));
// } else {
return DateTime.fromMillisecondsSinceEpoch(
if (projectId == 2 || projectId == 3) {
return DateTime.fromMillisecondsSinceEpoch(
int.parse(
date.substring(startIndex + start.length, endIndex),
),
isUtc: true)
.add(Duration(hours: 4));
} else {
return DateTime.fromMillisecondsSinceEpoch(
int.parse(
date.substring(startIndex + start.length, endIndex),
),
isUtc: true)
.add(Duration(hours: 3));
// }
}
} else {
return DateTime.now();
}

@ -19,7 +19,12 @@ 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/cache_consts.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/utils.dart';
import 'package:hmg_patient_app_new/features/notifications/models/resp_models/notification_response_model.dart';
import 'package:hmg_patient_app_new/presentation/notifications/notification_details_page.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:uuid/uuid.dart';
@ -523,6 +528,26 @@ class PushNotificationHandler {
_incomingCall(remoteMessage.data);
// showCallkitIncoming();
} else {
NotificationResponseModel notification = NotificationResponseModel();
notification.createdOn = DateUtil.convertDateToString(DateTime.now());
notification.isSentOn = DateUtil.convertDateToString(DateTime.now());
notification.messageTypeData = remoteMessage.data['picture'];
notification.messageType = notification.messageTypeData != null && notification.messageTypeData!.isNotEmpty ? "image" : "";
notification.message = remoteMessage.data['message'];
notification.notificationType = remoteMessage.data["NotificationType"].toString();
if (remoteMessage.data["NotificationType"] == "2") {
notification.videoURL = remoteMessage.data["VideoUrl"];
}
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).push(
CustomPageRoute(
page: NotificationDetailsPage(
notification: notification,
),
),
);
// GetNotificationsResponseModel notification = new GetNotificationsResponseModel();
//
// notification.createdOn = DateUtil.convertDateToString(DateTime.now());

@ -27,7 +27,7 @@ class RequestUtils {
}) {
bool fileNo = false;
if (nationId.isNotEmpty) {
final numericRegex = RegExp(r'^[0-9]+$');
final numericRegex = RegExp(r'^[0-9]+$');
fileNo = nationId.length < 10 && numericRegex.hasMatch(nationId);
//fileNo = nationId.length < 10 && nationId.isNumericOnly() ;
if (fileNo) {
@ -40,14 +40,23 @@ class RequestUtils {
if (zipCode == "0") {
request.patientMobileNumberOthers = phoneNumber;
} else {
request.patientMobileNumber = int.parse(phoneNumber);
// Remove any non-numeric characters before parsing
final numericPhone = phoneNumber.replaceAll(RegExp(r'[^0-9]'), '');
final parsedPhone = int.tryParse(numericPhone);
if (parsedPhone != null) {
request.patientMobileNumber = parsedPhone;
} else {
// If parsing fails, use as string in Others field
request.patientMobileNumberOthers = phoneNumber;
}
}
}
request.oTPSendType = otpTypeEnum.toInt(); // could map OTPTypeEnum if needed
request.zipCode = zipCode; // or countryCode if defined elsewhere
if (isForRegister) {
request.patientIdentificationID = int.parse(nationId);
final parsedNationId = int.tryParse(nationId.replaceAll(RegExp(r'[^0-9]'), ''));
request.patientIdentificationID = parsedNationId ?? 0;
request.searchType = 1;
request.isHijri = calenderType.toInt;
request.patientID = patientId;
@ -56,7 +65,7 @@ class RequestUtils {
request.isDentalAllowedBackend = false;
} else {
if (fileNo) {
request.patientID = patientId ?? int.parse(nationId);
request.patientID = patientId ?? (int.tryParse(nationId.replaceAll(RegExp(r'[^0-9]'), '')) ?? 0);
request.patientIdentificationID = request.nationalID;
request.searchType = 2;
} else {
@ -77,15 +86,15 @@ class RequestUtils {
static dynamic getCommonRequestWelcome(
{required String phoneNumber,
required OTPTypeEnum otpTypeEnum,
required String? deviceToken,
required bool patientOutSA,
required String? loginTokenID,
RegistrationDataModelPayload? registeredData,
int? patientId,
required String nationIdText,
required String countryCode,
required int loginType}) {
required OTPTypeEnum otpTypeEnum,
required String? deviceToken,
required bool patientOutSA,
required String? loginTokenID,
RegistrationDataModelPayload? registeredData,
int? patientId,
required String nationIdText,
required String countryCode,
required int loginType}) {
bool fileNo = false;
if (nationIdText.isNotEmpty) {
final numericRegex = RegExp(r'^[0-9]+$');
@ -97,8 +106,17 @@ class RequestUtils {
request.patientMobileNumberOthers = phoneNumber;
request.mobileNo = phoneNumber;
} else {
request.patientMobileNumber = int.parse(phoneNumber);
request.mobileNo = '0$phoneNumber';
// Remove any non-numeric characters before parsing
final numericPhone = phoneNumber.replaceAll(RegExp(r'[^0-9]'), '');
final parsedPhone = int.tryParse(numericPhone);
if (parsedPhone != null) {
request.patientMobileNumber = parsedPhone;
request.mobileNo = '0$numericPhone';
} else {
// If parsing fails, use as string in Others field
request.patientMobileNumberOthers = phoneNumber;
request.mobileNo = phoneNumber;
}
}
request.deviceToken = deviceToken;
request.projectOutSA = patientOutSA;
@ -112,8 +130,8 @@ class RequestUtils {
request.searchType = registeredData.searchType != null
? registeredData.searchType
: fileNo
? 1
: 2;
? 1
: 2;
request.patientID = registeredData.patientId ?? 0;
request.patientIdentificationID = request.nationalID = (registeredData.patientIdentificationId ?? 0);
request.dob = registeredData.dob;
@ -121,7 +139,8 @@ class RequestUtils {
log("nationIdText: ${nationIdText}");
} else {
if (fileNo) {
request.patientID = patientId ?? int.parse(nationIdText);
final numericNationId = nationIdText.replaceAll(RegExp(r'[^0-9]'), '');
request.patientID = patientId ?? (int.tryParse(numericNationId) ?? 0);
request.patientIdentificationID = request.nationalID = '0';
request.searchType = 2;
//TODO: Issue HEre is Not Login
@ -167,8 +186,17 @@ class RequestUtils {
request.patientMobileNumberOthers = mobileNumber;
request.mobileNo = mobileNumber;
} else {
request.patientMobileNumber = int.parse(mobileNumber);
request.mobileNo = '0$mobileNumber';
// Remove any non-numeric characters before parsing
final numericMobile = mobileNumber.replaceAll(RegExp(r'[^0-9]'), '');
final parsedMobile = int.tryParse(numericMobile);
if (parsedMobile != null) {
request.patientMobileNumber = parsedMobile;
request.mobileNo = '0$numericMobile';
} else {
// If parsing fails, use as string in Others field
request.patientMobileNumberOthers = mobileNumber;
request.mobileNo = mobileNumber;
}
}
}
request.projectOutSA = patientOutSA;
@ -259,16 +287,16 @@ class RequestUtils {
"Patientobject": {
"TempValue": true,
"PatientIdentificationType": (isDubai
? appState.getUserRegistrationPayload.patientIdentificationId?.toString().substring(0, 1)
: appState.getNHICUserData.idNumber!.substring(0, 1)) ==
"1"
? 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(),
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')
appState.getUserRegistrationPayload.zipCode == '+966')
? 0
: 1,
"FirstNameN": isDubai ? "..." : appState.getNHICUserData.firstNameAr,
@ -285,31 +313,31 @@ class RequestUtils {
"DateofBirthN": date,
"EmailAddress": emailAddress,
"SourceType": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode ||
appState.getUserRegistrationPayload.zipCode == '+966')
appState.getUserRegistrationPayload.zipCode == '+966')
? "1"
: "2",
"PreferredLanguage": appState.getLanguageCode() == "ar" ? (isDubai ? "1" : 1) : (isDubai ? "2" : 2),
"Marital": isDubai
? (maritalStatus == MaritalStatusTypeEnum.single
? '0'
: maritalStatus == MaritalStatusTypeEnum.married
? '1'
: '2')
? '0'
: maritalStatus == MaritalStatusTypeEnum.married
? '1'
: '2')
: (appState.getNHICUserData.maritalStatusCode == 'U'
? '0'
: appState.getNHICUserData.maritalStatusCode == 'M'
? '1'
: '2'),
? '0'
: appState.getNHICUserData.maritalStatusCode == 'M'
? '1'
: '2'),
},
"PatientIdentificationID":
isDubai ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : appState.getNHICUserData.idNumber.toString(),
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')
appState.getUserRegistrationPayload.zipCode == '+966')
? 0
: 1,
"isDentalAllowedBackend": appState.getUserRegistrationPayload.isDentalAllowedBackend,
@ -333,7 +361,9 @@ class RequestUtils {
request.sharedPatientId = 0;
request.sharedPatientIdentificationId = nationalIDorFile;
} else if (loginType == 2) {
request.sharedPatientId = int.parse(nationalIDorFile);
// Remove any non-numeric characters before parsing
final numericId = nationalIDorFile.replaceAll(RegExp(r'[^0-9]'), '');
request.sharedPatientId = int.tryParse(numericId) ?? 0;
request.sharedPatientIdentificationId = '';
}
request.searchType = loginType;
@ -344,4 +374,4 @@ class RequestUtils {
request.isDentalAllowedBackend = false;
return request;
}
}
}

@ -437,8 +437,8 @@ class Utils {
onCancelTap();
}
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
backgroundColor: AppColors.criticalActionColor,
borderColor: AppColors.criticalActionColor,
textColor: AppColors.whiteColor,
// icon: AppAssets.cancel,
// iconColor: AppColors.whiteColor,
@ -516,8 +516,8 @@ class Utils {
onCancelTap();
}
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
backgroundColor: AppColors.criticalActionColor,
borderColor: AppColors.criticalActionColor,
textColor: AppColors.whiteColor,
// icon: AppAssets.cancel,
// iconColor: AppColors.whiteColor,
@ -808,8 +808,8 @@ class Utils {
double? border,
double? borderRadius,
}) {
final iconH = height ?? 24.h;
final iconW = width ?? 24.w;
final double? iconW = (width == null && height == null) ? 24.w : width;
final double? iconH = (width == null && height == null) ? 24.h : height;
return PreserveImageColors(
child: Container(
decoration: BoxDecoration(
@ -851,7 +851,7 @@ class Utils {
/// Widget to build an image from network - automatically preserves colors in visual modes
static Widget buildImgWithNetwork({
required String url,
required String? url,
required Color iconColor,
bool isDisabled = false,
double? width,
@ -859,8 +859,15 @@ class Utils {
BoxFit fit = BoxFit.cover,
ImageErrorWidgetBuilder? errorBuilder,
}) {
final iconH = height ?? 24.h;
final iconW = width ?? 24.w;
final double? iconW = (width == null && height == null) ? 24.w : width;
final double? iconH = (width == null && height == null) ? 24.h : height;
if (url == null || url.isEmpty) {
return Utils.buildSvgWithAssets(
width: iconW ?? 24.w,
height: iconH ?? 24.h,
icon: AppAssets.noImageFound,
);
}
return PreserveImageColors(
child: Image.network(
url,
@ -870,7 +877,7 @@ class Utils {
errorBuilder: errorBuilder ??
(_, __, ___) {
//todo_section change the error builder icon that it is returning
return Utils.buildSvgWithAssets(width: iconW, height: iconH, icon: AppAssets.no_visit_icon);
return Utils.buildSvgWithAssets(width: iconW ?? 24.w, height: iconH ?? 24.h, icon: AppAssets.noImageFound);
},
),
);
@ -1057,9 +1064,42 @@ class Utils {
return isHavePrivilege;
}
static void openWebView({required String url}) {
Uri uri = Uri.parse(url);
launchUrl(uri, mode: LaunchMode.inAppBrowserView);
static Future<void> openWebView({required String url}) async {
try {
Uri uri = Uri.parse(url);
// Validate URL scheme for in-app browser
if (!uri.hasScheme || (!uri.scheme.startsWith('http'))) {
throw 'Invalid URL scheme. In-app browser only supports HTTP/HTTPS URLs';
}
// Check if URL can be launched
if (await canLaunchUrl(uri)) {
final launched = await launchUrl(
uri,
mode: LaunchMode.externalApplication,
);
if (!launched) {
// Fallback to external browser
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
} else {
// Fallback to external browser
await launchUrl(uri, mode: LaunchMode.externalApplication);
}
} catch (e) {
debugPrint('❌ Failed to open URL: $url - Error: $e');
// Try external browser as last resort
try {
final uri = Uri.parse(url);
await launchUrl(uri, mode: LaunchMode.externalApplication);
} catch (e2) {
debugPrint('❌ Failed to open URL in external browser: $e2');
// Optionally show user-friendly error message
}
}
}
static Color getCardBorderColor(int currentQueueStatus) {

@ -2,3 +2,9 @@ const Map configs = {
'ZOOM_SDK_KEY': 'b9T74nhfTg-ioP9urm970A',
'ZOOM_SDK_SECRET': 'KOzmjBNXQ1f4IPHpnngfL29uZvJMufSy2Fk8',
};
// const Map configs = {
// 'ZOOM_SDK_KEY': 'jYHoRpSUMTTefOwLq94Kyf1Kak513TUHpu78',
// 'ZOOM_SDK_SECRET': 'MOqi1zc18VFOMEaBDshFSHKLB0C2n9M0K48H',
// };

@ -3,6 +3,7 @@ import 'dart:developer';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:get_it/get_it.dart';
@ -30,6 +31,7 @@ import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_mode
import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/authentication/login.dart';
@ -823,7 +825,11 @@ class AuthenticationViewModel extends ChangeNotifier {
onSuccess: (dynamic respData) async {
try {
if (respData != null) {
dynamic data = await SelectDeviceByImeiRespModelElement.fromJson(respData.toJson());
SelectDeviceByImeiRespModelElement data = SelectDeviceByImeiRespModelElement.fromJson(respData.toJson());
if (data.mobile == null || data.mobile == "" || data.identificationNo == null || data.identificationNo == "") {
return;
}
_appState.setSelectDeviceByImeiRespModelElement(data);
LoaderBottomSheet.hideLoader();
@ -855,8 +861,6 @@ class AuthenticationViewModel extends ChangeNotifier {
}
Future<void> checkUserAuthentication({required OTPTypeEnum otpTypeEnum, Function(dynamic)? onSuccess, Function(String)? onError}) async {
// TODO: THIS SHOULD BE REMOVED LATER ON AND PASSED FROM APP STATE DIRECTLY INTO API CLIENT. BECAUSE THIS API ONLY NEEDS FEW PARAMS FROM USER
loginTypeEnum = otpTypeEnum == OTPTypeEnum.sms ? LoginTypeEnum.sms : LoginTypeEnum.whatsapp;
// if (phoneNumberController.text.isEmpty) {
@ -979,13 +983,15 @@ class AuthenticationViewModel extends ChangeNotifier {
// TODO: GET APP SMS SIGNATURE HERE
request.sMSSignature = await getSignature();
// Get Turnstile token if configured
final turnstileToken = await _turnstileService.getTurnstileToken();
if (turnstileToken != null) {
request.turnstileToken = turnstileToken;
} else {
request.turnstileToken = "";
}
// if(!kDebugMode) {
// // Get Turnstile token if configured
// final turnstileToken = await _turnstileService.getTurnstileToken();
// if (turnstileToken != null) {
// request.turnstileToken = turnstileToken;
// } else {
// request.turnstileToken = "";
// }
// }
if (isNeedLoading) {
LoaderBottomSheet.showLoader();
@ -998,7 +1004,10 @@ class AuthenticationViewModel extends ChangeNotifier {
sendActivationCodeReq: request, isRegister: checkIsUserComingForRegister(request: payload), languageID: 'er', isFormFamilyFile: isFormFamilyFile);
resultEither.fold(
(failure) async => await _errorHandlerService.handleError(failure: failure),
(failure) async {
LoaderBottomSheet.hideLoader();
await _errorHandlerService.handleError(failure: failure);
},
(apiResponse) async {
if (isFormFamilyFile) {
if (apiResponse.data['PatientShareRequestID'] != null && apiResponse.data['PatientShareRequestID'] == 0 && apiResponse.messageStatus == 1) {
@ -1239,6 +1248,7 @@ class AuthenticationViewModel extends ChangeNotifier {
_appState.setUserBloodGroup = activation.patientBlodType ?? "N/A";
_appState.setIsPaytabsEnabled(Utils.havePrivilege(120));
_appState.setIsPayfortHostedPageEnabled(Utils.havePrivilege(121));
_appState.setIsMyFatoorahEnabled(Utils.havePrivilege(123));
// Refresh privileges after user switching to ensure correct privileges are loaded
if (isSwitchUser) {
@ -1272,6 +1282,9 @@ class AuthenticationViewModel extends ChangeNotifier {
getIt.get<HabibWalletViewModel>().setIsWalletAmountToBeLoaded(true);
getIt.get<InsuranceViewModel>().setIsInsuranceDataToBeLoaded(true);
getIt.get<HmgServicesViewModel>().setHasVitalSignDataLoaded(false);
getIt.get<MedicalFileViewModel>().setIsSickLeavesDataNeedsReloading(true);
getIt.get<MedicalFileViewModel>().setIsMedicalReportsDataNeedsReloading(true);
getIt.get<PrescriptionsViewModel>().setIsPrescriptionDataNeedsReloading(true);
if (isUserAgreedBefore) {
LoaderBottomSheet.hideLoader();
navigateToHomeScreen();
@ -1784,6 +1797,7 @@ class AuthenticationViewModel extends ChangeNotifier {
_appState.setProjectsDetailList(projectDetailListModel);
_appState.setIsPaytabsEnabled(Utils.havePrivilege(120));
_appState.setIsPayfortHostedPageEnabled(Utils.havePrivilege(121));
_appState.setIsMyFatoorahEnabled(Utils.havePrivilege(123));
}
},
);

@ -2,7 +2,9 @@
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/app_state.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/dependencies.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/features/book_appointments/models/resp_models/appointment_nearest_gate_response_model.dart';
@ -30,7 +32,7 @@ abstract class BookAppointmentsRepo {
Future<Either<Failure, GenericApiModel<DoctorsProfileResponseModel>>> getDoctorProfile(int clinicID, int projectID, int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError});
Future<Either<Failure, GenericApiModel<dynamic>>> getDoctorRatingDetails(int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError});
Future<Either<Failure, GenericApiModel<dynamic>>> getDoctorRatingDetails(int doctorId, int projectID, {Function(dynamic)? onSuccess, Function(String)? onError});
Future<Either<Failure, GenericApiModel<dynamic>>> getDoctorFreeSlots(int clinicID, int projectID, int doctorId, bool isBookingForLiveCare,
{bool continueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError});
@ -181,6 +183,9 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo {
"IsSearchAppointmnetByClinicID": isContinueDentalPlan ? false : true,
"isDentalAllowedBackend": clinicID == 17 ? true : isContinueDentalPlan,
"IsGetNearAppointment": isNearest,
"gender": getIt.get<AppState>().isAuthenticated ? getIt.get<AppState>().getAuthenticatedUser()!.gender! : 0,
"age": getIt.get<AppState>().isAuthenticated ? getIt.get<AppState>().getAuthenticatedUser()!.age! : 0,
"DateofBirth": getIt.get<AppState>().isAuthenticated ? getIt.get<AppState>().getAuthenticatedUser()!.dateofBirth! : null,
if (isNearest) "SelectedDate": DateUtil.convertDateToString(DateTime.now()),
"License": true
};
@ -309,9 +314,10 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo {
}
@override
Future<Either<Failure, GenericApiModel<dynamic>>> getDoctorRatingDetails(int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError}) async {
Future<Either<Failure, GenericApiModel<dynamic>>> getDoctorRatingDetails(int doctorId, int projectID, {Function(dynamic)? onSuccess, Function(String)? onError}) async {
Map<String, dynamic> mapDevice = {
"DoctorID": doctorId,
"ProjectID": projectID,
"PatientID": 0,
"License": true,
"IsRegistered": true,

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
@ -404,6 +405,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
isWaitingAppointmentSelected = false;
isPatientRescheduleAppointment = false;
bodyTypes = [maleLaserCategory, femaleLaserCategory];
selectedChiefComplaintID = 0;
// getLocation();
notifyListeners();
}
@ -420,14 +422,14 @@ class BookAppointmentsViewModel extends ChangeNotifier {
setIsNearestAppointmentSelected(bool isNearestAppointmentSelected) {
this.isNearestAppointmentSelected = isNearestAppointmentSelected;
if (isNearestAppointmentSelected) {
for (var group in doctorsListGrouped) {
group.sort((a, b) {
var aSlot = a.nearestFreeSlot;
var bSlot = b.nearestFreeSlot;
if (aSlot == null || bSlot == null) return 0;
return DateUtil.convertStringToDate(aSlot).compareTo(DateUtil.convertStringToDate(bSlot));
});
}
// for (var group in doctorsListGrouped) {
// group.sort((a, b) {
// var aSlot = a.nearestFreeSlot;
// var bSlot = b.nearestFreeSlot;
// if (aSlot == null || bSlot == null) return 0;
// return DateUtil.convertStringToDate(aSlot).compareTo(DateUtil.convertStringToDate(bSlot));
// });
// }
} else {
// for (var group in doctorsListGrouped) {
// group.sort((a, b) {
@ -675,7 +677,6 @@ class BookAppointmentsViewModel extends ChangeNotifier {
(failure) async {
isDoctorsListLoading = false;
if (onError != null) onError(LocaleKeys.noDoctorFound.tr());
notifyListeners();
},
(apiResponse) {
@ -690,7 +691,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
clearSearchFilters();
getFiltersFromDoctorList();
_groupDoctorsList();
setIsNearestAppointmentSelected(isNearest);
// setIsNearestAppointmentSelected(isNearest);
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
@ -806,7 +807,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
doctorDetailsList.clear();
notifyListeners();
final result = await bookAppointmentsRepo.getDoctorRatingDetails(selectedDoctor.doctorID ?? 0, onSuccess: onSuccess, onError: onError);
final result = await bookAppointmentsRepo.getDoctorRatingDetails(selectedDoctor.doctorID ?? 0, selectedDoctor.projectID ?? 0, onSuccess: onSuccess, onError: onError);
result.fold(
(failure) async {
@ -842,67 +843,73 @@ class BookAppointmentsViewModel extends ChangeNotifier {
}
Future<void> getDoctorFreeSlots({bool isBookingForLiveCare = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
docFreeSlots.clear();
DateTime date;
final DateFormat formatter = DateFormat('HH:mm', "en-US");
final DateFormat dateFormatter = DateFormat('yyyy-MM-dd');
Map<DateTime, List> _eventsParsed;
final result = await bookAppointmentsRepo.getDoctorFreeSlots(
selectedDoctor.clinicID ?? 0,
selectedDoctor.projectID ?? 0,
selectedDoctor.doctorID ?? 0,
isBookingForLiveCare,
continueDentalPlan: isContinueDentalPlan,
onError: onError,
);
result.fold(
(failure) async {
print(failure);
if (onError != null) {
onError(failure.message);
}
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage ?? "Unknown error occurred");
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
if (apiResponse.data['FreeTimeSlots'] == null || apiResponse.data['FreeTimeSlots'].isEmpty) {
onError!("No free slots available".tr());
return;
// try {
docFreeSlots.clear();
DateTime date;
final DateFormat formatter = DateFormat('HH:mm', "en-US");
// final DateFormat dateFormatter = DateFormat('yyyy-MM-dd');
// Map<DateTime, List> _eventsParsed;
final result = await bookAppointmentsRepo.getDoctorFreeSlots(
selectedDoctor.clinicID ?? 0,
selectedDoctor.projectID ?? 0,
selectedDoctor.doctorID ?? 0,
isBookingForLiveCare,
continueDentalPlan: isContinueDentalPlan,
onError: onError,
);
result.fold(
(failure) async {
print(failure);
if (onError != null) {
onError(failure.message);
}
initialSlotDuration = apiResponse.data["InitialSlotDuration"];
freeSlotsResponse = apiResponse.data['FreeTimeSlots'];
isWaitingAppointmentAvailable = apiResponse.data["IsAllowToBookWaitingAppointment"];
// isWaitingAppointmentAvailable = true;
freeSlotsResponse.forEach((element) {
// date = (isLiveCareSchedule != null && isLiveCareSchedule)
// ? DateUtil.convertStringToDate(element)
// :
date = DateUtil.convertStringToDateSaudiTimezone(element, int.parse(selectedDoctor.projectID.toString()));
slotsList.add(FreeSlot(date, ['slot']));
docFreeSlots.add(TimeSlot(isoTime: formatter.format(date), start: new DateTime(date.year, date.month, date.day, 0, 0, 0, 0), end: date, vidaDate: element));
});
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage ?? "Unknown error occurred");
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
if (apiResponse.data['FreeTimeSlots'] == null || apiResponse.data['FreeTimeSlots'].isEmpty) {
onError!("No free slots available".tr());
return;
}
initialSlotDuration = apiResponse.data["InitialSlotDuration"];
freeSlotsResponse = apiResponse.data['FreeTimeSlots'];
isWaitingAppointmentAvailable = apiResponse.data["IsAllowToBookWaitingAppointment"];
// isWaitingAppointmentAvailable = true;
freeSlotsResponse.forEach((element) {
// date = (isLiveCareSchedule != null && isLiveCareSchedule)
// ? DateUtil.convertStringToDate(element)
// :
date = DateUtil.convertStringToDateSaudiTimezone(element, int.parse(selectedDoctor.projectID.toString()));
slotsList.add(FreeSlot(date, ['slot']));
docFreeSlots.add(TimeSlot(isoTime: formatter.format(date), start: new DateTime(date.year, date.month, date.day, 0, 0, 0, 0), end: date, vidaDate: element));
});
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
}
},
);
},
);
// } catch(ex) {
// if (onError != null) {
// onError(ex.toString());
// }
// }
}
Future<void> getLiveCareDoctorFreeSlots({bool isBookingForLiveCare = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
docFreeSlots.clear();
DateTime date;
final DateFormat formatter = DateFormat('HH:mm', "en-US");
final DateFormat dateFormatter = DateFormat('yyyy-MM-dd');
Map<DateTime, List> _eventsParsed;
// final DateFormat dateFormatter = DateFormat('yyyy-MM-dd');
// Map<DateTime, List> _eventsParsed;
final result = await bookAppointmentsRepo.getLiveCareDoctorFreeSlots(
selectedDoctor.clinicID ?? 0, selectedLiveCareClinic.serviceID ?? 0, selectedDoctor.projectID ?? 0, selectedDoctor.doctorID ?? 0, isBookingForLiveCare,

@ -15,6 +15,19 @@ import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:permission_handler/permission_handler.dart';
/// Class to store feedback attachment data
class FeedbackAttachment {
final String base64String;
final File file;
final String fileName;
FeedbackAttachment({
required this.base64String,
required this.file,
required this.fileName,
});
}
class ContactUsViewModel extends ChangeNotifier {
ContactUsRepo contactUsRepo;
ErrorHandlerService errorHandlerService;
@ -35,7 +48,7 @@ class ContactUsViewModel extends ChangeNotifier {
String? chatRequestID;
List<String> feedbackAttachmentList = [];
List<FeedbackAttachment> feedbackAttachmentList = [];
List<COCItem> cocItemsList = [];
bool isCOCItemsListLoading = false;
@ -168,13 +181,20 @@ class ContactUsViewModel extends ChangeNotifier {
notifyListeners();
}
addFeedbackAttachment(String attachmentPath) {
feedbackAttachmentList.add(attachmentPath);
addFeedbackAttachment(String base64String, File file) {
// Extract filename from file path
final fileName = file.path.split('/').last;
feedbackAttachmentList.add(FeedbackAttachment(
base64String: base64String,
file: file,
fileName: fileName,
));
notifyListeners();
}
removeFeedbackAttachment(String attachmentPath) {
feedbackAttachmentList.remove(attachmentPath);
removeFeedbackAttachment(FeedbackAttachment attachment) {
feedbackAttachmentList.remove(attachment);
notifyListeners();
}
@ -282,7 +302,7 @@ class ContactUsViewModel extends ChangeNotifier {
Future<void> insertCOCItem({required String subject, required String message, Function(dynamic)? onSuccess, Function(String)? onError}) async {
RequestInsertCOCItem requestInsertCOCItem = RequestInsertCOCItem();
requestInsertCOCItem.attachment = feedbackAttachmentList.isNotEmpty ? feedbackAttachmentList.first : "";
requestInsertCOCItem.attachment = feedbackAttachmentList.isNotEmpty ? feedbackAttachmentList.first.base64String : "";
requestInsertCOCItem.title = subject;
requestInsertCOCItem.details = message;
requestInsertCOCItem.cOCTypeName = selectedFeedbackType.id.toString();
@ -291,7 +311,7 @@ class ContactUsViewModel extends ChangeNotifier {
requestInsertCOCItem.isUserLoggedIn = true;
requestInsertCOCItem.projectID = 0;
requestInsertCOCItem.patientName = "${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}";
requestInsertCOCItem.fileName = "Image 1";
requestInsertCOCItem.fileName = feedbackAttachmentList.isNotEmpty ? feedbackAttachmentList.first.fileName : "";
requestInsertCOCItem.appVersion = ApiConsts.appVersionID;
requestInsertCOCItem.uILanguage = appState.isArabic() ? "ar" : "en";
requestInsertCOCItem.browserInfo = Platform.localHostname;

@ -30,7 +30,36 @@ class HabibWalletViewModel extends ChangeNotifier {
num habibWalletAmount = 0;
num walletRechargeAmount = 0;
String notesText = "";
bool isWalletAmountToBeLoaded = true;
bool isWalletAmountToBeLoaded = false;
bool isLakumInfoToBeLoaded = false;
// ── Balance visibility (show/hide like a password field) ──────────────────
bool isWalletAmountHidden = true;
bool isLakumPointsHidden = true;
// Track whether data has already been fetched (to avoid re-calling APIs).
bool isWalletAmountFetched = false;
bool isLakumInfoFetched = false;
void toggleWalletAmountVisibility() {
isWalletAmountHidden = !isWalletAmountHidden;
notifyListeners();
// On first unhide, fetch the data if not already fetched.
if (!isWalletAmountHidden && !isWalletAmountFetched) {
isWalletAmountToBeLoaded = true;
getPatientBalanceAmount();
}
}
void toggleLakumPointsVisibility() {
isLakumPointsHidden = !isLakumPointsHidden;
notifyListeners();
// On first unhide, fetch the data if not already fetched.
if (!isLakumPointsHidden && !isLakumInfoFetched) {
isLakumInfoToBeLoaded = true;
getLakumAccountInformation();
}
}
bool isBottomSheetContentLoading = false;
@ -70,6 +99,8 @@ class HabibWalletViewModel extends ChangeNotifier {
bool isLakumAccountInfoLoading = false;
LakumInquiryInformationResponseModel? lakumAccountInfo;
// Get all flattened transactions sorted by date descending
List<PointsDetails> getAllLakumTransactions() {
if (lakumAccountInfo?.gainedPointsAmountPerYear == null) {
@ -328,6 +359,8 @@ class HabibWalletViewModel extends ChangeNotifier {
if (isWalletAmountToBeLoaded) {
isWalletAmountLoading = true;
habibWalletAmount = 0;
}
if (isLakumInfoToBeLoaded) {
isLakumAccountInfoLoading = true;
yahalaAccountNumber = "";
}
@ -399,6 +432,11 @@ class HabibWalletViewModel extends ChangeNotifier {
notifyListeners();
}
setIsLakumInfoToBeLoaded(bool value) {
isLakumInfoToBeLoaded = value;
notifyListeners();
}
String getSelectedRechargeTypeValue() {
switch (selectedRechargeType) {
case 1:
@ -437,6 +475,7 @@ class HabibWalletViewModel extends ChangeNotifier {
isWalletAmountLoading = false;
isWalletAmountToBeLoaded = false;
isWalletAmountFetched = true;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
@ -540,7 +579,7 @@ class HabibWalletViewModel extends ChangeNotifier {
}
Future<void> getLakumAccountInformation({Function(dynamic)? onSuccess, Function(String)? onError}) async {
if (!isWalletAmountToBeLoaded) {
if (!isLakumInfoToBeLoaded) {
return;
}
@ -555,6 +594,8 @@ class HabibWalletViewModel extends ChangeNotifier {
(failure) async {
yahalaAccountNumber = "0";
isLakumAccountInfoLoading = false;
isLakumInfoFetched = true;
isLakumInfoToBeLoaded = false;
notifyListeners(); // 675534
},
(apiResponse) async {
@ -581,6 +622,8 @@ class HabibWalletViewModel extends ChangeNotifier {
);
}
isLakumAccountInfoLoading = false;
isLakumInfoFetched = true;
isLakumInfoToBeLoaded = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);

@ -67,7 +67,7 @@ abstract class HmgServicesRepo {
Future<Either<Failure, GenericApiModel<Covid19GetPaymentInfo>>> getCovidPaymentInfo(String procedureID, int projectID);
Future<Either<Failure, GenericApiModel<List<VitalSignResModel>>>> getPatientVitalSign();
Future<Either<Failure, GenericApiModel<List<VitalSignResModel>>>> getPatientVitalSign( num? transID, int? projectID);
}
class HmgServicesRepoImp implements HmgServicesRepo {
@ -917,8 +917,11 @@ class HmgServicesRepoImp implements HmgServicesRepo {
}
@override
Future<Either<Failure, GenericApiModel<List<VitalSignResModel>>>> getPatientVitalSign() async {
Future<Either<Failure, GenericApiModel<List<VitalSignResModel>>>> getPatientVitalSign(num? transID, int? projectID) async {
Map<String, dynamic> requestBody = {
"TransID": transID,
"TransNo": transID,
"ProjectID": projectID,
};
try {

@ -895,37 +895,59 @@ class HmgServicesViewModel extends ChangeNotifier {
Future<void> getPatientVitalSign({
Function(dynamic)? onSuccess,
Function(String)? onError,
num? transNo,
int? projectID,
bool forceRefresh = false,
}) async {
if (hasVitalSignDataLoaded) return;
isVitalSignLoading = true;
notifyListeners();
final bool isSpecificQuery = (transNo != null || projectID != null);
if (!isSpecificQuery && hasVitalSignDataLoaded && !forceRefresh) {
if (onSuccess != null) {
onSuccess(GenericApiModel<List<VitalSignResModel>>(
messageStatus: 1,
data: vitalSignList,
));
}
return;
}
final result = await hmgServicesRepo.getPatientVitalSign();
if (!isSpecificQuery) {
isVitalSignLoading = true;
notifyListeners();
}
final result = await hmgServicesRepo.getPatientVitalSign(transNo, projectID);
result.fold(
(failure) async {
isVitalSignLoading = false;
notifyListeners();
if (!isSpecificQuery) {
isVitalSignLoading = false;
notifyListeners();
}
await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.message);
}
},
(apiResponse) {
isVitalSignLoading = false;
if (apiResponse.messageStatus == 1) {
vitalSignList = apiResponse.data ?? [];
hasVitalSignDataLoaded = true;
if (vitalSignList.isNotEmpty) {
getIt.get<SymptomsCheckerViewModel>().setSelectedHeight(vitalSignList.first.heightCm);
getIt.get<SymptomsCheckerViewModel>().setSelectedWeight(vitalSignList.first.weightKg);
if (!isSpecificQuery) {
isVitalSignLoading = false;
if (apiResponse.messageStatus == 1) {
vitalSignList = apiResponse.data ?? [];
hasVitalSignDataLoaded = true;
if (vitalSignList.isNotEmpty) {
getIt.get<SymptomsCheckerViewModel>().setSelectedHeight(vitalSignList.first.heightCm);
getIt.get<SymptomsCheckerViewModel>().setSelectedWeight(vitalSignList.first.weightKg);
}
}
notifyListeners();
}
if (apiResponse.messageStatus == 1) {
if (onSuccess != null) {
onSuccess(apiResponse);
}
} else {
notifyListeners();
if (onError != null) {
onError(apiResponse.errorMessage ?? 'Unknown error');
}

@ -101,9 +101,7 @@ class ImmediateLiveCareRepoImp implements ImmediateLiveCareRepo {
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final respObject = response['GetERAppointmentFeesList'];
final liveCareFeesObj = LiveCareImmediateAppointmentFeesList.fromJson(respObject);
apiResponse = GenericApiModel<LiveCareImmediateAppointmentFeesList>(
messageStatus: messageStatus,
statusCode: statusCode,

@ -59,16 +59,25 @@ class ImmediateLiveCareViewModel extends ChangeNotifier {
bool isShowNotEligibleDialog = false;
String notEligibleErrorMsg = "";
bool isTermsAccepted = false;
initImmediateLiveCare() {
_appState = getIt<AppState>();
immediateLiveCareClinicsList = [];
mainLiveCareClinicList = [];
isImmediateLiveCareClinicsLoading = true;
isTermsAccepted = false;
liveCareSelectedCallType = 0; // 1- Video, 2- Audio, 3- Phone
immediateLiveCareSelectedClinic = GetLiveCareClinicListResponseModel();
liveCareImmediateAppointmentFeesList = LiveCareImmediateAppointmentFeesList();
}
onTermsAccepted() {
isTermsAccepted = !isTermsAccepted;
notifyListeners();
}
setLiveCareSelectedCallType(int value) {
liveCareSelectedCallType = value;
notifyListeners();
@ -126,7 +135,9 @@ class ImmediateLiveCareViewModel extends ChangeNotifier {
result.fold(
(failure) async {
onError!(failure.message);
if(onError != null) {
onError(failure.message);
}
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {

@ -11,7 +11,7 @@ import 'package:hmg_patient_app_new/features/insurance/models/resp_models/upload
import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class InsuranceRepo {
Future<Either<Failure, GenericApiModel<List<PatientInsuranceDetailsResponseModel>>>> getPatientInsuranceDetails();
Future<Either<Failure, GenericApiModel<List<PatientInsuranceDetailsResponseModel>>>> getPatientInsuranceDetails({bool isFromHomePage = false});
Future<Either<Failure, GenericApiModel<List<PatientInsuranceCardHistoryResponseModel>>>> getPatientInsuranceCardHistory();
@ -35,8 +35,10 @@ class InsuranceRepoImp implements InsuranceRepo {
InsuranceRepoImp({required this.loggerService, required this.apiClient});
@override
Future<Either<Failure, GenericApiModel<List<PatientInsuranceDetailsResponseModel>>>> getPatientInsuranceDetails() async {
Map<String, dynamic> mapDevice = {};
Future<Either<Failure, GenericApiModel<List<PatientInsuranceDetailsResponseModel>>>> getPatientInsuranceDetails({bool isFromHomePage = false}) async {
Map<String, dynamic> mapDevice = {
"IsFromHomePage": isFromHomePage
};
try {
GenericApiModel<List<PatientInsuranceDetailsResponseModel>>? apiResponse;

@ -32,15 +32,11 @@ class InsuranceViewModel extends ChangeNotifier {
InsuranceViewModel({required this.insuranceRepo, required this.errorHandlerService});
initInsuranceProvider() {
debugPrint("InsuranceViewModel: initInsuranceProvider called, isInsuranceDataToBeLoaded=$isInsuranceDataToBeLoaded");
initInsuranceProvider({bool isFromHomePage = false}) {
if (isInsuranceDataToBeLoaded) {
patientInsuranceList.clear();
isInsuranceLoading = true;
debugPrint("InsuranceViewModel: Calling getPatientInsuranceDetails API");
getPatientInsuranceDetails();
} else {
debugPrint("InsuranceViewModel: Skipping API call, isInsuranceDataToBeLoaded is false");
getPatientInsuranceDetails(isFromHomePage: isFromHomePage);
}
patientInsuranceCardHistoryList.clear();
isInsuranceHistoryLoading = true;
@ -80,19 +76,16 @@ class InsuranceViewModel extends ChangeNotifier {
notifyListeners();
}
Future<void> getPatientInsuranceDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async {
Future<void> getPatientInsuranceDetails({bool isFromHomePage = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
if (!isInsuranceDataToBeLoaded) {
debugPrint("InsuranceViewModel: getPatientInsuranceDetails - skipping, flag is false");
return;
}
debugPrint("InsuranceViewModel: getPatientInsuranceDetails - making API call");
final result = await insuranceRepo.getPatientInsuranceDetails();
final result = await insuranceRepo.getPatientInsuranceDetails(isFromHomePage: isFromHomePage);
result.fold(
// (failure) async => await errorHandlerService.handleError(failure: failure),
(failure) async {
debugPrint("InsuranceViewModel: API call failed - ${failure.toString()}");
isInsuranceLoading = false;
isInsuranceDataToBeLoaded = true;
isInsuranceExpired = false;
@ -102,11 +95,9 @@ class InsuranceViewModel extends ChangeNotifier {
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
debugPrint("InsuranceViewModel: API returned error status");
isInsuranceDataToBeLoaded = false;
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
debugPrint("InsuranceViewModel: API success - received ${apiResponse.data?.length ?? 0} insurance records");
patientInsuranceList = apiResponse.data!;
isInsuranceLoading = false;
isInsuranceDataToBeLoaded = false;
@ -115,7 +106,6 @@ class InsuranceViewModel extends ChangeNotifier {
isInsuranceExpired = DateTime.now().isAfter(
DateUtil.convertStringToDate(patientInsuranceList.first.cardValidTo),
);
debugPrint("InsuranceViewModel: Insurance card expired: $isInsuranceExpired");
}
isInsuranceExpiryBannerShown = isInsuranceExpired;

@ -209,12 +209,12 @@ class LabViewModel extends ChangeNotifier {
final result = await labRepo.getLabResultReportPDF(labOrder: labOrder);
result.fold(
(failure) async => await errorHandlerService.handleError(
failure: failure,
onOkPressed: () {
onError!(failure.message);
},
),
// (failure) async => await errorHandlerService.handleError(
(failure) async {
if (onError != null) {
onError(failure.message);
}
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage!);

@ -29,7 +29,11 @@ class MedicalFileViewModel extends ChangeNotifier {
bool isPatientSickLeavePDFLoading = false;
bool isPatientMedicalReportsListLoading = false;
bool isPatientAllergiesListLoading = false;
bool _isFamilyFilesLoading = false;
bool get isFamilyFilesLoading => _isFamilyFilesLoading;
set setIsFamilyFilesLoading(bool value) => _isFamilyFilesLoading = value;
MedicalFileRepo medicalFileRepo;
ErrorHandlerService errorHandlerService;
@ -41,7 +45,6 @@ class MedicalFileViewModel extends ChangeNotifier {
List<SickLeaveList> patientSickLeavesViewList = [];
bool isSickLeavesSortByClinic = true;
bool isSickLeavesDataNeedsReloading = true;
List<GetAllergiesResponseModel> patientAllergiesList = [];
@ -57,6 +60,7 @@ class MedicalFileViewModel extends ChangeNotifier {
List<MedicalReportList> patientMedicalReportsViewList = [];
bool isMedicalReportsSortByClinic = true;
bool isMedicalReportsDataNeedsReloading = true;
List<PatientAppointmentHistoryResponseModel> patientMedicalReportAppointmentHistoryList = [];
PatientAppointmentHistoryResponseModel? patientMedicalReportSelectedAppointment;
@ -164,6 +168,16 @@ class MedicalFileViewModel extends ChangeNotifier {
notifyListeners();
}
setIsSickLeavesDataNeedsReloading(bool value) {
isSickLeavesDataNeedsReloading = value;
notifyListeners();
}
setIsMedicalReportsDataNeedsReloading(bool value) {
isMedicalReportsDataNeedsReloading = value;
notifyListeners();
}
setIsPatientSickLeaveListLoading(bool val) {
if (val && isSickLeavesDataNeedsReloading) {
patientSickLeaveList.clear();
@ -188,7 +202,7 @@ class MedicalFileViewModel extends ChangeNotifier {
}
setIsPatientMedicalReportsLoading(bool val) {
if (val) {
if (val && isMedicalReportsDataNeedsReloading) {
onMedicalReportTabChange(0);
patientMedicalReportList.clear();
patientMedicalReportsByClinic.clear();
@ -196,8 +210,8 @@ class MedicalFileViewModel extends ChangeNotifier {
patientMedicalReportsViewList.clear();
patientMedicalReportPDFBase64 = "";
isMedicalReportsSortByClinic = true;
isPatientMedicalReportsListLoading = val;
}
isPatientMedicalReportsListLoading = val;
notifyListeners();
}
@ -369,6 +383,10 @@ class MedicalFileViewModel extends ChangeNotifier {
}
Future<void> getPatientMedicalReportList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
if (!isMedicalReportsDataNeedsReloading) {
return;
}
patientMedicalReportList.clear();
patientMedicalReportRequestedList.clear();
patientMedicalReportReadyList.clear();
@ -381,6 +399,7 @@ class MedicalFileViewModel extends ChangeNotifier {
(failure) async => await errorHandlerService.handleError(
failure: failure,
onOkPressed: () {
isMedicalReportsDataNeedsReloading = true;
onError!(failure.message);
},
),
@ -396,6 +415,7 @@ class MedicalFileViewModel extends ChangeNotifier {
}
onMedicalReportTabChange(0);
isPatientMedicalReportsListLoading = false;
isMedicalReportsDataNeedsReloading = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
@ -431,21 +451,42 @@ class MedicalFileViewModel extends ChangeNotifier {
}
Future<void> getFamilyFiles({int? status, Function(dynamic)? onSuccess, Function(String)? onError}) async {
setIsFamilyFilesLoading = true;
final shouldShowLoader = patientFamilyFiles.isEmpty;
if (shouldShowLoader) {
LoaderBottomSheet.showLoader();
}
final result = await medicalFileRepo.getPatientFamilyFiles(status, _appState.getSuperUserID != null ? _appState.getSuperUserID! : _appState.getAuthenticatedUser()!.patientId!);
result.fold(
(failure) async => await errorHandlerService.handleError(
failure: failure,
onOkPressed: () {
onError!(failure.message);
},
),
(failure) async {
setIsFamilyFilesLoading = false;
await errorHandlerService.handleError(
failure: failure,
onOkPressed: () {
if (shouldShowLoader) {
LoaderBottomSheet.hideLoader();
}
onError!(failure.message);
},
);
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
setIsFamilyFilesLoading = false;
// _dialogService.showErrorBottomSheet(message: apiResponse.errorMessage!, onOkPressed: () {});
if (shouldShowLoader) {
LoaderBottomSheet.hideLoader();
}
} else if (apiResponse.messageStatus == 1) {
if (apiResponse.data != null) {
// Add current user as the first active family file
final currentUser = _appState.getAuthenticatedUser()!;
final currentUserFamilyFile = FamilyFileResponseModelLists(
patientId: currentUser.patientId,
@ -525,6 +566,12 @@ class MedicalFileViewModel extends ChangeNotifier {
pendingFamilyFiles = newPendingFamilyFiles;
}
// Clear loading state on success
setIsFamilyFilesLoading = false;
if (shouldShowLoader) {
LoaderBottomSheet.hideLoader();
}
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
@ -662,13 +709,10 @@ class MedicalFileViewModel extends ChangeNotifier {
}
Future<void> handleFamilyFileRequestOTPVerification() async {
LoaderBottomSheet.showLoader();
if (!_appState.getIsChildLoggedIn) {
await getFamilyFiles(status: 0);
await getAllPendingRecordsByResponseId();
}
LoaderBottomSheet.hideLoader();
}
Future<void> removeFileFromFamilyMembers({int? id}) async {

@ -161,6 +161,20 @@ class AppointmentViaRegionViewmodel extends ChangeNotifier {
page: DentalChiefComplaintsPage(),
),
);
} else {
if (appState.getAuthenticatedUser()!.age! > 12) {
navigationService.push(
CustomPageRoute(
page: DentalChiefComplaintsPage(),
),
);
} else {
navigationService.push(
CustomPageRoute(
page: SelectDoctorPage(),
),
);
}
}
}
if (clinicId == 253) {

@ -25,7 +25,7 @@ import 'models/resp_models/appointment_details_resp_model.dart';
abstract class MyAppointmentsRepo {
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientAppointments(
{required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false});
{required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false, bool isForTimeLine = false});
Future<Either<Failure, GenericApiModel<PatientAppointmentShareResponseModel>>> getPatientShareAppointment(
{required int projectID, required int clinicID, required String appointmentNo, required bool isLiveCareAppointment});
@ -105,14 +105,15 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
@override
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientAppointments(
{required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false}) async {
{required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false, bool isForTimeLine = false}) async {
Map<String, dynamic> mapDevice = {
"IsActiveAppointment": isActiveAppointment,
"IsComingFromCOC": false,
"isForUpcomming": false,
"IsForMedicalReport": false,
"IsForArrived": isArrivedAppointments,
"IsIrisPrescription": isForEyeMeasurement
"IsIrisPrescription": isForEyeMeasurement,
"IsForTimeLine": isForTimeLine
};
try {
@ -172,9 +173,9 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
try {
final list = response['OnlineCheckInAppointments'];
patientShareObj = (list == null || list.isEmpty) ? PatientAppointmentShareResponseModel() : PatientAppointmentShareResponseModel.fromJson(list[0]);
patientShareObj.isCash = response["IsCash"];
patientShareObj.isEligible = response["IsEligible"];
patientShareObj.isInsured = response["IsInsured"];
patientShareObj.isCash = isLiveCareAppointment ? list[0]["IsCash"] : response["IsCash"];
patientShareObj.isEligible = isLiveCareAppointment ? list[0]["IsEligible"] : response["IsEligible"];
patientShareObj.isInsured = isLiveCareAppointment ? list[0]["IsInsured"] : response["IsInsured"];
// if (list == null || list.isEmpty) {
// throw Exception("patient share list is empty");

@ -55,6 +55,8 @@ class MyAppointmentsViewModel extends ChangeNotifier {
bool isAppointmentDataToBeLoaded = true;
bool isMyDoctorsDataToBeLoaded = true;
bool isArrivedAppointmentDataLoaded = false;
bool isFullArrivedAppointmentsLoaded = false;
bool isEyeMeasurementsAppointmentsLoading = false;
@ -76,7 +78,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
List<PatientAppointmentHistoryResponseModel> patientUpcomingAppointmentsHistoryList = [];
List<PatientAppointmentHistoryResponseModel> patientArrivedAppointmentsHistoryList = [];
List<PatientAppointmentHistoryResponseModel> patientTimelineAppointmentsList = [];
// List<PatientAppointmentHistoryResponseModel> patientAllArrivedAppointmentsHistoryList = [];
List<PatientAppointmentHistoryResponseModel> patientMyDoctorsList = [];
@ -172,11 +174,12 @@ class MyAppointmentsViewModel extends ChangeNotifier {
patientAppointmentsHistoryList.clear();
patientUpcomingAppointmentsHistoryList.clear();
patientArrivedAppointmentsHistoryList.clear();
patientTimelineAppointmentsList.clear();
// patientAllArrivedAppointmentsHistoryList.clear();
patientEyeMeasurementsAppointmentsHistoryList.clear();
isMyAppointmentsLoading = true;
isTimeLineAppointmentsLoading = true;
patientMyDoctorsList.clear();
isFullArrivedAppointmentsLoaded = false;
}
isTamaraDetailsLoading = true;
isAppointmentPatientShareLoading = true;
@ -266,7 +269,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
);
}
Future<void> getPatientAppointments(bool isActiveAppointment, bool isArrivedAppointments, {Function(dynamic)? onSuccess, Function(String)? onError}) async {
Future<void> getPatientAppointments(bool isActiveAppointment, bool isArrivedAppointments, {bool isForTimeLine = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
if (patientArrivedAppointmentsHistoryList.isNotEmpty) {
isPatientHasQueueAppointment = false;
notifyListeners();
@ -275,15 +278,19 @@ class MyAppointmentsViewModel extends ChangeNotifier {
getPatientAppointmentQueueDetails();
}
}
if (!isAppointmentDataToBeLoaded) return;
debugPrint('getPatientAppointments CALLED!!!');
// Skip API call if full data is already loaded and we're not requesting timeline view
if (!isForTimeLine && isFullArrivedAppointmentsLoaded && !isAppointmentDataToBeLoaded) {
return;
}
if (!isAppointmentDataToBeLoaded) return;
patientAppointmentsByClinic.clear();
patientAppointmentsByHospital.clear();
patientAppointmentsViewList.clear();
patientTimelineAppointmentsList.clear();
// patientAllArrivedAppointmentsHistoryList.clear();
filteredAppointmentList.clear();
patientAppointmentsHistoryList.clear();
patientUpcomingAppointmentsHistoryList.clear();
@ -292,7 +299,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
final results = await Future.wait([
myAppointmentsRepo.getPatientAppointments(isActiveAppointment: isActiveAppointment, isArrivedAppointments: isArrivedAppointments),
myAppointmentsRepo.getPatientAppointments(isActiveAppointment: false, isArrivedAppointments: true),
myAppointmentsRepo.getPatientAppointments(isActiveAppointment: false, isArrivedAppointments: true, isForTimeLine: isForTimeLine),
]);
results[0].fold(
@ -321,6 +328,11 @@ class MyAppointmentsViewModel extends ChangeNotifier {
patientArrivedAppointmentsHistoryList = apiResponse.data!;
isMyAppointmentsLoading = false;
isAppointmentDataToBeLoaded = false;
if (!isForTimeLine) {
// patientAllArrivedAppointmentsHistoryList = apiResponse.data!;
isArrivedAppointmentDataLoaded = true;
isFullArrivedAppointmentsLoaded = true;
}
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
@ -347,13 +359,73 @@ class MyAppointmentsViewModel extends ChangeNotifier {
}
// }
debugPrint('Upcoming Appointments: ${patientUpcomingAppointmentsHistoryList.length}');
debugPrint('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}');
debugPrint('All Appointments: ${patientAppointmentsHistoryList.length}');
getFiltersForSelectedAppointmentList(filteredAppointmentList);
notifyListeners();
}
changeTabToArrived() {
if (isFullArrivedAppointmentsLoaded && patientUpcomingAppointmentsHistoryList.isEmpty && patientArrivedAppointmentsHistoryList.isNotEmpty && selectedTabIndex == 0) {
selectedTabIndex = 1;
updateListWRTTab(1);
notifyListeners();
}
}
/// Loads full arrived appointments list if not already loaded
/// This is called when navigating to My Appointments page from landing page
Future<void> loadFullArrivedAppointmentsIfNeeded({Function(dynamic)? onSuccess, Function(String)? onError}) async {
// Skip if already loaded
if (isFullArrivedAppointmentsLoaded) {
// Auto-switch to Arrived tab if no upcoming appointments (even when data is already loaded)
if (patientUpcomingAppointmentsHistoryList.isEmpty && patientArrivedAppointmentsHistoryList.isNotEmpty && selectedTabIndex == 0) {
selectedTabIndex = 1;
updateListWRTTab(1);
notifyListeners();
}
return;
}
setIsAppointmentsHistoryLoading(true);
// Fetch full arrived appointments (isForTimeLine = false)
final result = await myAppointmentsRepo.getPatientAppointments(isActiveAppointment: false, isArrivedAppointments: true, isForTimeLine: false);
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// Handle error
} else if (apiResponse.messageStatus == 1) {
patientArrivedAppointmentsHistoryList = apiResponse.data!;
// patientAllArrivedAppointmentsHistoryList = apiResponse.data!;
isArrivedAppointmentDataLoaded = true;
isFullArrivedAppointmentsLoaded = true;
// Rebuild the combined list
patientAppointmentsHistoryList.clear();
patientAppointmentsHistoryList.addAll(patientUpcomingAppointmentsHistoryList);
patientAppointmentsHistoryList.addAll(patientArrivedAppointmentsHistoryList);
isMyAppointmentsLoading = false;
// Auto-switch to Arrived tab if no upcoming appointments
if (patientUpcomingAppointmentsHistoryList.isEmpty && patientArrivedAppointmentsHistoryList.isNotEmpty && selectedTabIndex == 0) {
selectedTabIndex = 1;
updateListWRTTab(1);
} else {
// Update filtered list based on current tab
updateListWRTTab(selectedTabIndex);
}
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
void getFiltersForSelectedAppointmentList(List<PatientAppointmentHistoryResponseModel> filteredAppointmentList) {
availableFilters.clear();
if (filteredAppointmentList.isEmpty == true) return;
@ -362,9 +434,9 @@ class MyAppointmentsViewModel extends ChangeNotifier {
availableFilters.add(AppointmentListingFilters.LIVECARE);
}
if (filteredAppointmentList.any((element) => element.isLiveCareAppointment == false)) {
availableFilters.add(AppointmentListingFilters.WALKIN);
}
// if (filteredAppointmentList.any((element) => element.isLiveCareAppointment == false)) {
// availableFilters.add(AppointmentListingFilters.WALKIN);
// }
if (filteredAppointmentList.any((element) => AppointmentType.isArrived(element) == true)) {
availableFilters.add(AppointmentListingFilters.ARRIVED);
@ -699,7 +771,11 @@ class MyAppointmentsViewModel extends ChangeNotifier {
} else if (apiResponse.messageStatus == 1) {
patientMyDoctorsList = apiResponse.data!;
isPatientMyDoctorsLoading = false;
// if (!isTop8) {
isMyDoctorsDataToBeLoaded = false;
// }
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
@ -801,15 +877,15 @@ class MyAppointmentsViewModel extends ChangeNotifier {
selectedFilter = [];
// if(previouslySelectedTab == selectedTabIndex ) return;
switch (index) {
// case 0:
// filteredAppointmentList.clear();
// filteredAppointmentList.addAll(patientAppointmentsHistoryList);
// break;
case 0:
filteredAppointmentList.clear();
filteredAppointmentList.addAll(patientAppointmentsHistoryList);
break;
case 1:
filteredAppointmentList.clear();
filteredAppointmentList.addAll(patientUpcomingAppointmentsHistoryList);
break;
case 2:
case 1:
filteredAppointmentList.clear();
filteredAppointmentList.addAll(patientArrivedAppointmentsHistoryList);
break;
@ -835,11 +911,12 @@ class MyAppointmentsViewModel extends ChangeNotifier {
this.end = end;
isDateFilterSelected = true;
List<PatientAppointmentHistoryResponseModel> sourceList = [];
if (selectedTabIndex == 0) {
sourceList = patientAppointmentsHistoryList;
} else if (selectedTabIndex == 1) {
// if (selectedTabIndex == 0) {
// sourceList = patientAppointmentsHistoryList;
// } else
if (selectedTabIndex == 0) {
sourceList = patientUpcomingAppointmentsHistoryList;
} else if (selectedTabIndex == 2) {
} else if (selectedTabIndex == 1) {
sourceList = patientArrivedAppointmentsHistoryList;
}
// if (isDateFilterSelected) sourceList = filteredAppointmentList;
@ -1299,7 +1376,6 @@ class MyAppointmentsViewModel extends ChangeNotifier {
);
} catch (e) {
// Ignore errors if reminder doesn't exist
debugPrint("No existing ${type.displayName} to remove");
}
}
}

@ -0,0 +1,197 @@
import 'dart:developer';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/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/myFatoorah_config.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/payfort/models/payfort_request_insert_model.dart';
import 'package:hmg_patient_app_new/features/payfort/models/payfort_response_insert_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart';
import 'package:hmg_patient_app_new/generated/codegen_loader.g.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:myfatoorah_flutter/myfatoorah_flutter.dart';
class MyFatoorahViewModel extends ChangeNotifier {
final AppState appState;
final ErrorHandlerService errorHandlerService;
final PayfortRepo payfortRepo;
MyFatoorahViewModel({
required this.appState,
required this.errorHandlerService,
required this.payfortRepo,
});
// Payment Method ID
// 2: Credit/Debit Card
// 6: Mada
initiateCardPayment({
num amount = 10.0,
paymentDescription = "",
String displayCurrencyIso = MFCurrencyISO.KUWAIT_KWD,
required String paymentOption,
required String liveServiceID,
required String isScheduleLiveCare,
required String appointmentDate,
required String appointmentNo,
required String doctorID,
required String fileNumber,
required String serviceID,
required String projectID,
Function(MFGetPaymentStatusResponse)? onSuccess,
Function(String)? onError,
}) async {
LoaderBottomSheet.showLoader();
await MFSDK.init(MyFatoorahConfig.configAPIKey, ApiConsts.myFatoorahCountry, ApiConsts.myFatoorahEnvironmentType);
MFSDK.setUpActionBar(toolBarTitle: paymentDescription, toolBarTitleColor: "#ffffff", toolBarBackgroundColor: '#ED1C2B', isShowToolBar: true);
var request = MFExecutePaymentRequest(paymentMethodId: paymentOption.toLowerCase() == "mada" ? 6 : 2, invoiceValue: amount);
request.displayCurrencyIso = displayCurrencyIso;
request.customerName = ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? "Test test" : "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}";
request.customerEmail = "CustID_${appState.getAuthenticatedUser()!.patientId}@HMG.com";
await MFSDK.executePayment(request, appState.isArabic() ? MFLanguage.ARABIC : MFLanguage.ENGLISH, (invoiceId) async {
PayfortRequestInsertModel payfortRequestInsertModel = PayfortRequestInsertModel();
payfortRequestInsertModel.commandType = "SALE";
payfortRequestInsertModel.amount = amount;
payfortRequestInsertModel.customerEmail = getIt.get<AppState>().getAuthenticatedUser()!.emailAddress;
payfortRequestInsertModel.orderDescription = paymentDescription;
payfortRequestInsertModel.serviceID = serviceID;
payfortRequestInsertModel.paymentOption = paymentOption;
payfortRequestInsertModel.clientRequestID = invoiceId;
payfortRequestInsertModel.projectID = projectID;
payfortRequestInsertModel.fileNumber = getIt.get<AppState>().getAuthenticatedUser()!.patientId.toString();
payfortRequestInsertModel.customerName = "${getIt.get<AppState>().getAuthenticatedUser()!.firstName} ${getIt.get<AppState>().getAuthenticatedUser()!.lastName}";
payfortRequestInsertModel.currency = getIt.get<AppState>().getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED";
payfortRequestInsertModel.liveServiceID = liveServiceID;
payfortRequestInsertModel.latitude = getIt.get<AppState>().userLat.toString();
payfortRequestInsertModel.longitude = getIt.get<AppState>().userLong.toString();
payfortRequestInsertModel.merchantReference = invoiceId;
payfortRequestInsertModel.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken);
payfortRequestInsertModel.language = getIt.get<AppState>().isArabic() ? "ar" : "en";
payfortRequestInsertModel.integrationId = "6"; // PayTabs integration ID
payfortRequestInsertModel.isSchedule = isScheduleLiveCare;
payfortRequestInsertModel.appointmentDate = appointmentDate;
payfortRequestInsertModel.appointmentNo = appointmentNo;
payfortRequestInsertModel.doctorID = doctorID;
// Call request insert API before payment
await paytabsRequestInsert(payfortRequestInsertModel: payfortRequestInsertModel);
LoaderBottomSheet.hideLoader();
}).then((value) async {
var valJson = value.toString();
print(valJson);
// // Create PayfortResponseInsertModel (same as Payfort implementation)
PayfortResponseInsertModel payfortResponseInsertModel = PayfortResponseInsertModel();
payfortResponseInsertModel.fortId = value.invoiceTransactions!.first.transactionId ?? "";
payfortResponseInsertModel.commandType = "SALE";
// payfortResponseInsertModel.amount = paytabsTransactionResponseModel.tranTotal ?? 0;
payfortResponseInsertModel.amount = amount;
payfortResponseInsertModel.sadadOLP = "";
payfortResponseInsertModel.paymentOption = value.invoiceTransactions!.first.paymentGateway ?? "";
payfortResponseInsertModel.customerIP = "";
payfortResponseInsertModel.eCI = "";
payfortResponseInsertModel.responseMessage = value.invoiceTransactions!.first.transactionStatus ?? "";
payfortResponseInsertModel.cardNumber = value.invoiceTransactions!.first.cardNumber ?? "";
payfortResponseInsertModel.cardBin = "";
payfortResponseInsertModel.installmentsNumber = 0;
payfortResponseInsertModel.status = (value.invoiceStatus ?? "").toLowerCase() == "paid" ? "14" : "00";
payfortResponseInsertModel.merchantRef = value.invoiceId.toString();
payfortResponseInsertModel.isRefund = false;
payfortResponseInsertModel.remmeberMe = true;
payfortResponseInsertModel.reconciliationReference = "";
// Call response insert API after payment
await paytabsResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel);
if ((value.invoiceStatus ?? "").toLowerCase() == "paid") {
if (onSuccess != null) {
onSuccess(value);
}
} else {
if (onError != null) {
onError(LocaleKeys.paymentFailedPleaseTryAgain.tr());
}
}
}).catchError((error) {
if (onError != null) {
onError(error.message);
}
});
}
executeApplePayPayment() {
MFApplepay.executeApplePayPayment(onInvoiceCreated: (invoiceID) {
log(
invoiceID.toString(),
);
}).then((value) {
log(
value.toString(),
);
}).catchError((error) {
log(error.message);
});
}
initApplePayPayment(num invoiceValue) async {
await MFSDK.init(MyFatoorahConfig.configAPIKey, ApiConsts.myFatoorahCountry, ApiConsts.myFatoorahEnvironmentType);
await MFSDK.initSession(MFInitiateSessionRequest(), MFLanguage.ENGLISH).then((value) async {
var executePaymentRequest = MFExecutePaymentRequest(invoiceValue: invoiceValue, sessionId: value.sessionId, displayCurrencyIso: MFCurrencyISO.SAUDIARABIA_SAR);
MFApplepay.setupApplePay(value, executePaymentRequest, MFLanguage.ENGLISH, merchantName: "Dr Sulaiman Al Habib");
}).catchError((error) {
log(error.message);
});
}
Future<void> paytabsRequestInsert({required PayfortRequestInsertModel payfortRequestInsertModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await payfortRepo.payfortRequestInsert(payfortRequestInsertModel: payfortRequestInsertModel);
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<void> paytabsResponseInsert({required PayfortResponseInsertModel payfortResponseInsertModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await payfortRepo.payfortResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel);
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);
}
}
},
);
}
}

@ -0,0 +1,123 @@
import 'package:flutter/material.dart';
/// Model representing a remotely-configurable "occasion" theme
/// (e.g. Saudi National Day, Founding Day, Eid, Ramadan, ...).
///
/// Everything here is fully data-driven — nothing about a specific
/// occasion (name, color, dates, banner asset) is hardcoded in the app.
/// All values are supplied via Firebase Remote Config (or the bundled
/// default JSON fallback used before the first successful fetch).
@immutable
class OccasionConfigModel {
/// Unique identifier of the occasion (e.g. "national_day"). Used only
/// for logging/analytics — no app logic branches on this value.
final String key;
/// Master kill-switch. When false, the app must look exactly like the
/// default/current theme, regardless of any other field below.
final bool enabled;
/// Optional activation window. When both are present, the theme is only
/// considered active if `DateTime.now()` falls within [startDate, endDate].
/// When null, the [enabled] flag alone controls activation.
final DateTime? startDate;
final DateTime? endDate;
/// Hex string (e.g. "#00894A") for the occasion's primary brand color.
/// When null, the app's default primary color is preserved.
final String? primaryColorHex;
/// Remote (or bundled asset path) image rendered as a decorative
/// background/watermark, drawn using [backgroundImageOpacity].
final String? backgroundOpacityImage;
/// Opacity (0.0 - 1.0) applied to [backgroundOpacityImage].
final double backgroundImageOpacity;
/// Remote (or bundled asset path) image rendered as the occasion's main
/// header/banner (e.g. the National Day greeting banner), drawn at full
/// opacity on top of [backgroundOpacityImage].
final String? headerImage;
const OccasionConfigModel({
required this.key,
required this.enabled,
this.startDate,
this.endDate,
this.primaryColorHex,
this.backgroundOpacityImage,
this.backgroundImageOpacity = 1.0,
this.headerImage,
});
/// Fully inert config — identical to the app's current/default look.
factory OccasionConfigModel.disabled() => const OccasionConfigModel(
key: 'none',
enabled: false,
startDate: null,
endDate: null,
primaryColorHex: null,
backgroundOpacityImage: null,
backgroundImageOpacity: 1.0,
headerImage: null,
);
factory OccasionConfigModel.fromJson(Map<String, dynamic> json) {
return OccasionConfigModel(
key: (json['key'] as String?) ?? 'none',
enabled: (json['enabled'] as bool?) ?? false,
startDate: _parseDate(json['start_date']),
endDate: _parseDate(json['end_date']),
primaryColorHex: json['primary_color'] as String?,
backgroundOpacityImage: json['background_opacity_image'] as String?,
backgroundImageOpacity: (json['background_image_opacity'] as num?)?.toDouble() ?? 1.0,
headerImage: json['header_image'] as String?,
);
}
static DateTime? _parseDate(dynamic value) {
if (value == null) return null;
if (value is String && value.isNotEmpty) {
return DateTime.tryParse(value);
}
return null;
}
/// Whether "now" falls inside the configured activation window.
/// If no window is configured, the [enabled] flag alone decides.
bool isWithinActiveWindow([DateTime? now]) {
final current = now ?? DateTime.now();
if (startDate != null && current.isBefore(startDate!)) return false;
if (endDate != null && current.isAfter(endDate!)) return false;
return true;
}
/// Parsed [Color] from [primaryColorHex], or null if not configured.
Color? get primaryColor => _hexToColor(primaryColorHex);
/// Lighter/translucent variant of [primaryColor] — 11% opacity of the same
/// hue (e.g. for chip/badge backgrounds). Derived automatically from
/// [primaryColor]; not an independent remote-config field. Null unless
/// [primaryColor] is configured.
Color? get secondaryColor => primaryColor?.withValues(alpha: 0.11);
static Color? _hexToColor(String? hex) {
if (hex == null || hex.isEmpty) return null;
var value = hex.trim().replaceFirst('#', '');
if (value.length == 6) value = 'FF$value';
final parsed = int.tryParse(value, radix: 16);
return parsed != null ? Color(parsed) : null;
}
Map<String, dynamic> toJson() => {
'key': key,
'enabled': enabled,
'start_date': startDate?.toIso8601String(),
'end_date': endDate?.toIso8601String(),
'primary_color': primaryColorHex,
'background_opacity_image': backgroundOpacityImage,
'background_image_opacity': backgroundImageOpacity,
'header_image': headerImage,
};
}

@ -0,0 +1,109 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart' show Color;
import 'package:hmg_patient_app_new/features/occasion_theme/occasion_config_model.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'package:hmg_patient_app_new/services/remote_config_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
/// Drives the fully remote-config-based "occasion" theming (National Day,
/// Founding Day, Eid, Ramadan, ...). Nothing about a specific occasion is
/// hardcoded here — every color/asset/date comes from [OccasionConfigModel],
/// which is parsed from Firebase Remote Config.
///
/// Mirrors the existing dark-mode pattern in ProfileSettingsViewModel:
/// a static flag on [AppColors] is kept in sync so every widget using
/// AppColors.primaryRedColor (even ones with no BuildContext/Provider
/// access) reflects the active occasion instantly.
class OccasionThemeViewModel extends ChangeNotifier {
static const String _cachedConfigKey = 'cached_occasion_config_json';
final RemoteConfigService _remoteConfigService;
final CacheService _cacheService;
final LoggerService _loggerService;
OccasionThemeViewModel({
required RemoteConfigService remoteConfigService,
required CacheService cacheService,
required LoggerService loggerService,
}) : _remoteConfigService = remoteConfigService,
_cacheService = cacheService,
_loggerService = loggerService;
OccasionConfigModel _config = OccasionConfigModel.disabled();
OccasionConfigModel get config => _config;
/// True only when enabled AND (if a window is configured) within it.
bool get isOccasionActive => _config.enabled && _config.isWithinActiveWindow();
String? get backgroundOpacityImage => isOccasionActive ? _config.backgroundOpacityImage : null;
double get backgroundImageOpacity => _config.backgroundImageOpacity;
/// The occasion's main header/banner image (e.g. National Day greeting
/// banner), rendered at full opacity. Null unless an occasion is active.
String? get headerImage => isOccasionActive ? _config.headerImage : null;
/// Lighter/translucent variant of the active occasion's primary color
/// (11% opacity) — e.g. for chip/badge backgrounds. Null unless an
/// occasion is active.
Color? get secondaryColor => isOccasionActive ? _config.secondaryColor : null;
/// Call once at app startup (before the first frame) to restore the last
/// known-good config instantly (offline-safe), then kick off a remote
/// fetch in the background.
Future<void> initialize() async {
_restoreCachedConfig();
_applyToAppColors();
try {
await _remoteConfigService.initialize();
await refresh();
} catch (ex) {
_loggerService.logError('OccasionThemeViewModel.initialize error: $ex');
}
}
/// Fetches the latest occasion config from Firebase Remote Config and
/// applies it. Safe to call again later (e.g. on app resume) to pick up
/// changes without requiring an app update.
Future<void> refresh() async {
try {
await _remoteConfigService.fetchAndActivate();
final rawJson = _remoteConfigService.occasionConfigRawJson;
_config = OccasionConfigModel.fromJson(json.decode(rawJson) as Map<String, dynamic>);
await _cacheService.saveString(key: _cachedConfigKey, value: rawJson);
_applyToAppColors();
notifyListeners();
} catch (ex) {
_loggerService.logError('OccasionThemeViewModel.refresh error: $ex');
// Keep whatever config was already applied (cached/disabled) — never
// let a remote-config failure change the app's appearance unexpectedly.
}
}
void _restoreCachedConfig() {
try {
final cachedJson = _cacheService.getString(key: _cachedConfigKey);
if (cachedJson == null || cachedJson.isEmpty) {
_config = OccasionConfigModel.disabled();
return;
}
_config = OccasionConfigModel.fromJson(json.decode(cachedJson) as Map<String, dynamic>);
} catch (ex) {
_loggerService.logError('OccasionThemeViewModel._restoreCachedConfig error: $ex');
_config = OccasionConfigModel.disabled();
}
}
/// Keeps AppColors in sync so non-Provider call sites (AppColors.primaryRedColor)
/// pick up the occasion color exactly like the existing dark-mode flag does.
void _applyToAppColors() {
AppColors.isOccasionThemeActive = isOccasionActive;
AppColors.occasionPrimaryColor = _config.primaryColor;
AppColors.occasionSecondaryColor = _config.secondaryColor;
}
}

@ -3,6 +3,7 @@ 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/common_models/tamara_request_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';
@ -34,6 +35,8 @@ abstract class PayfortRepo {
Future<Either<Failure, GenericApiModel<dynamic>>> payfortRequestInsert({required PayfortRequestInsertModel payfortRequestInsertModel});
Future<Either<Failure, GenericApiModel<dynamic>>> payfortResponseInsert({required PayfortResponseInsertModel payfortResponseInsertModel});
Future<Either<Failure, GenericApiModel<dynamic>>> tamaraRequestInsert({required TamaraRequestModel tamaraRequestModel});
}
class PayfortRepoImp implements PayfortRepo {
@ -55,7 +58,7 @@ class PayfortRepoImp implements PayfortRepo {
try {
final list = response;
if (list == null || list.isEmpty) {
throw Exception("payfort list is empty");
throw Exception("No project configuration found.");
}
final applePayConfigurationResponse = PayfortProjectDetailsRespModel.fromJson(list[0]);
@ -350,4 +353,31 @@ class PayfortRepoImp implements PayfortRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel>> tamaraRequestInsert({required TamaraRequestModel tamaraRequestModel}) async {
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(TAMARA_REQUEST_INSERT, body: tamaraRequestModel.toJson(), onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
}, onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
}, isAllowAny: true, isPaymentServices: true);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}

@ -9,6 +9,7 @@ import 'package:flutter_amazonpaymentservices/flutter_amazonpaymentservices.dart
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/tamara_request_model.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart';
@ -116,6 +117,25 @@ class PayfortViewModel extends ChangeNotifier {
);
}
Future<void> tamaraRequestInsert({required TamaraRequestModel tamaraRequestModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await payfortRepo.tamaraRequestInsert(tamaraRequestModel: tamaraRequestModel);
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) {
// payfortProjectDetailsRespModel = apiResponse.data!;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
Future<void> payfortResponseInsert({required PayfortResponseInsertModel payfortResponseInsertModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await payfortRepo.payfortResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel);
@ -398,7 +418,7 @@ class PayfortViewModel extends ChangeNotifier {
payfortRequestInsertModel.sHAResponsePhase = payfortProjectDetailsRespModel!.shaResponse;
payfortRequestInsertModel.signature = sdkTokenResponse!.signature;
payfortRequestInsertModel.language = getIt.get<AppState>().isArabic() ? "ar" : "en";
payfortRequestInsertModel.integrationId = "1";
payfortRequestInsertModel.integrationId = "1"; // Payfort integration ID
payfortRequestInsertModel.isSchedule = isScheduleLiveCare;
payfortRequestInsertModel.appointmentDate = appointmentDate;
payfortRequestInsertModel.appointmentNo = appointmentNo;

@ -11,26 +11,53 @@ import 'package:flutter_paytabs_bridge/PaymentSdkTransactionType.dart';
import 'package:flutter_paytabs_bridge/flutter_paytabs_bridge.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/tamara_request_model.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/paytabs_config.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/payfort/models/payfort_project_details_resp_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart';
import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart';
import 'package:hmg_patient_app_new/features/payfort/models/payfort_request_insert_model.dart';
import 'package:hmg_patient_app_new/features/payfort/models/payfort_response_insert_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
class PayTabsViewModel extends ChangeNotifier {
late PaymentSdkConfigurationDetails paymentConfiguration;
final AppState appState;
final PayfortRepo payfortRepo;
final ErrorHandlerService errorHandlerService;
late PaytabsTransactionResponseModel paytabsTransactionResponseModel;
PayfortProjectDetailsRespModel? paytabsProjectDetailsRespModel;
PayTabsViewModel({
required this.appState,
required this.payfortRepo,
required this.errorHandlerService,
});
setPaymentConfiguration(String paymentDescription, num amount, {bool isAddCard = false}) {
setPaymentConfiguration(String paymentDescription, num amount, int serviceID, int projectID, {bool isAddCard = false}) async {
String cartID = DateTime.now().millisecondsSinceEpoch.toString();
LoaderBottomSheet.showLoader();
await getPayTabsConfigurations(serviceId: serviceID, projectId: projectID);
paymentConfiguration = PaymentSdkConfigurationDetails(
profileId: PaymentSdkDefaultConfig.profileID,
serverKey: PaymentSdkDefaultConfig.serverKey,
clientKey: PaymentSdkDefaultConfig.clientKey,
transactionType: PaymentSdkTransactionType.SALE,
profileId: paytabsProjectDetailsRespModel!.merchantIdentifier,
serverKey: paytabsProjectDetailsRespModel!.accessCode,
clientKey: paytabsProjectDetailsRespModel!.shaResponse,
// profileId: PaymentSdkDefaultConfig.profileID,
// serverKey: PaymentSdkDefaultConfig.serverKey,
// clientKey: PaymentSdkDefaultConfig.clientKey,
transactionType: PaymentSdkTransactionType.SALE,
cartId: cartID.substring((cartID.length - 5), cartID.length),
cartDescription: paymentDescription,
merchantName: PaymentSdkDefaultConfig.defaultMerchantName,
@ -43,7 +70,8 @@ class PayTabsViewModel extends ChangeNotifier {
amount: amount.toDouble(),
showBillingInfo: false,
forceShippingInfo: false,
currencyCode: PaymentSdkDefaultConfig.defaultCurrency,
// currencyCode: PaymentSdkDefaultConfig.defaultCurrency,
currencyCode: getIt.get<AppState>().getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
merchantCountryCode: PaymentSdkDefaultConfig.defaultMerchantCountryCode,
billingDetails: BillingDetails(
"${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" ?? "HMG Patient",
@ -97,12 +125,17 @@ class PayTabsViewModel extends ChangeNotifier {
notifyListeners();
}
setAlternatePaymentConfiguration(String paymentDescription, num amount, {bool isAddCard = false}) {
setAlternatePaymentConfiguration(String paymentDescription, num amount, int serviceID, int projectID, {bool isAddCard = false}) async {
String cartID = DateTime.now().millisecondsSinceEpoch.toString();
LoaderBottomSheet.showLoader();
await getPayTabsConfigurations(serviceId: serviceID, projectId: projectID);
paymentConfiguration = PaymentSdkConfigurationDetails(
profileId: PaymentSdkDefaultConfig.profileID,
serverKey: PaymentSdkDefaultConfig.serverKey,
clientKey: PaymentSdkDefaultConfig.clientKey,
profileId: paytabsProjectDetailsRespModel!.merchantIdentifier,
serverKey: paytabsProjectDetailsRespModel!.accessCode,
clientKey: paytabsProjectDetailsRespModel!.shaResponse,
transactionType: PaymentSdkTransactionType.SALE,
cartId: cartID.substring((cartID.length - 5), cartID.length),
cartDescription: paymentDescription,
@ -112,7 +145,8 @@ class PayTabsViewModel extends ChangeNotifier {
amount: amount.toDouble(),
showBillingInfo: false,
forceShippingInfo: false,
currencyCode: PaymentSdkDefaultConfig.defaultCurrency,
// currencyCode: PaymentSdkDefaultConfig.defaultCurrency,
currencyCode: getIt.get<AppState>().getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
merchantCountryCode: PaymentSdkDefaultConfig.defaultMerchantCountryCode,
billingDetails: BillingDetails("${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" ?? "HMG Patient",
"CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", appState.getAuthenticatedUser()!.mobileNumber ?? "0000000000", "Riyadh", "SA", "Riyadh", "Riyadh", "12626"),
@ -152,6 +186,42 @@ class PayTabsViewModel extends ChangeNotifier {
notifyListeners();
}
Future<void> paytabsRequestInsert({required PayfortRequestInsertModel payfortRequestInsertModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await payfortRepo.payfortRequestInsert(payfortRequestInsertModel: payfortRequestInsertModel);
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<void> paytabsResponseInsert({required PayfortResponseInsertModel payfortResponseInsertModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await payfortRepo.payfortResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel);
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);
}
}
},
);
}
startApplePayPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) async {
bool _callbackFired = false; // prevent duplicate callback invocations
@ -189,44 +259,180 @@ class PayTabsViewModel extends ChangeNotifier {
});
}
startCardPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) {
startCardPayment({
required String paymentOption,
required String liveServiceID,
required String isScheduleLiveCare,
required String appointmentDate,
required String appointmentNo,
required String doctorID,
required String transactionID,
required String amount,
required String fileNumber,
required String orderDescription,
required String serviceID,
required String projectID,
Function(PaytabsTransactionResponseModel)? onSuccess,
Function(String)? onError,
}) async {
bool _callbackFired = false; // prevent duplicate callback invocations
FlutterPaytabsBridge.startCardPayment(paymentConfiguration, (event) {
if (_callbackFired) return; // ignore any subsequent callback calls
try {
// LoaderBottomSheet.showLoader();
final transactionDetails = event["data"] as Map<Object?, Object?>?;
if (event["status"] == "success") {
_callbackFired = true;
// Cast Map<Object?, Object?> to Map<String, dynamic>
final data = Map<String, dynamic>.from(event["data"] as Map);
paytabsTransactionResponseModel = PaytabsTransactionResponseModel.fromJson(data);
if (paytabsTransactionResponseModel.isSuccess!) {
onSuccess!(paytabsTransactionResponseModel);
} else {
// Transaction was processed but failed
final reason =
transactionDetails?["payResponseReturn"] ?? transactionDetails?["responseMessage"] ?? transactionDetails?["message"] ?? "Unknown error";
final responseCode = transactionDetails?["responseCode"] ?? "";
final errorMessage =
responseCode.toString().isNotEmpty ? "Transaction failed (Code: $responseCode): $reason" : "Transaction failed: $reason";
onError!(errorMessage);
// Create PayfortRequestInsertModel (same as Payfort implementation)
PayfortRequestInsertModel payfortRequestInsertModel = PayfortRequestInsertModel();
payfortRequestInsertModel.commandType = "SALE";
payfortRequestInsertModel.amount = num.parse(amount);
payfortRequestInsertModel.customerEmail = getIt.get<AppState>().getAuthenticatedUser()!.emailAddress;
payfortRequestInsertModel.orderDescription = orderDescription;
payfortRequestInsertModel.serviceID = serviceID;
payfortRequestInsertModel.paymentOption = paymentOption;
payfortRequestInsertModel.clientRequestID = transactionID;
payfortRequestInsertModel.projectID = projectID;
payfortRequestInsertModel.fileNumber = getIt.get<AppState>().getAuthenticatedUser()!.patientId.toString();
payfortRequestInsertModel.customerName = "${getIt.get<AppState>().getAuthenticatedUser()!.firstName} ${getIt.get<AppState>().getAuthenticatedUser()!.lastName}";
payfortRequestInsertModel.currency = getIt.get<AppState>().getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED";
payfortRequestInsertModel.liveServiceID = liveServiceID;
payfortRequestInsertModel.latitude = getIt.get<AppState>().userLat.toString();
payfortRequestInsertModel.longitude = getIt.get<AppState>().userLong.toString();
payfortRequestInsertModel.merchantReference = transactionID;
payfortRequestInsertModel.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken);
payfortRequestInsertModel.language = getIt.get<AppState>().isArabic() ? "ar" : "en";
payfortRequestInsertModel.integrationId = "2"; // PayTabs integration ID
payfortRequestInsertModel.isSchedule = isScheduleLiveCare;
payfortRequestInsertModel.appointmentDate = appointmentDate;
payfortRequestInsertModel.appointmentNo = appointmentNo;
payfortRequestInsertModel.doctorID = doctorID;
// Call request insert API before payment
await paytabsRequestInsert(payfortRequestInsertModel: payfortRequestInsertModel);
LoaderBottomSheet.hideLoader();
// Start PayTabs payment
FlutterPaytabsBridge.startCardPayment(paymentConfiguration, (event) async {
if (_callbackFired) return; // ignore any subsequent callback calls
final transactionDetails = event["data"] as Map<Object?, Object?>?;
if (event["status"] == "success") {
_callbackFired = true;
LoaderBottomSheet.showLoader();
// Cast Map<Object?, Object?> to Map<String, dynamic>
final data = Map<String, dynamic>.from(event["data"] as Map);
paytabsTransactionResponseModel = PaytabsTransactionResponseModel.fromJson(data);
// Create PayfortResponseInsertModel (same as Payfort implementation)
PayfortResponseInsertModel payfortResponseInsertModel = PayfortResponseInsertModel();
payfortResponseInsertModel.fortId = paytabsTransactionResponseModel.transactionReference ?? "";
payfortResponseInsertModel.commandType = paytabsTransactionResponseModel.transactionType ?? "SALE";
// payfortResponseInsertModel.amount = paytabsTransactionResponseModel.tranTotal ?? 0;
payfortResponseInsertModel.amount = num.parse(amount);
payfortResponseInsertModel.sadadOLP = "";
payfortResponseInsertModel.paymentOption = paytabsTransactionResponseModel.paymentInfo?.paymentMethod ?? "";
payfortResponseInsertModel.customerIP = "";
payfortResponseInsertModel.eCI = "";
payfortResponseInsertModel.responseMessage = paytabsTransactionResponseModel.payResponseReturn ?? "";
payfortResponseInsertModel.cardNumber = paytabsTransactionResponseModel.paymentInfo!.paymentDescription;
payfortResponseInsertModel.cardBin = "";
payfortResponseInsertModel.installmentsNumber = 0;
payfortResponseInsertModel.status = paytabsTransactionResponseModel.isSuccess! ? "14" : "00";
payfortResponseInsertModel.merchantRef = transactionID;
payfortResponseInsertModel.isRefund = false;
payfortResponseInsertModel.remmeberMe = true;
payfortResponseInsertModel.reconciliationReference = "";
// Call response insert API after payment
await paytabsResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel);
LoaderBottomSheet.hideLoader();
if (paytabsTransactionResponseModel.isSuccess!) {
if (onSuccess != null) {
onSuccess(paytabsTransactionResponseModel);
}
} else {
// Transaction was processed but failed
final reason = transactionDetails?["payResponseReturn"] ?? transactionDetails?["responseMessage"] ?? transactionDetails?["message"] ?? "Unknown error";
final responseCode = transactionDetails?["responseCode"] ?? "";
final errorMessage = responseCode.toString().isNotEmpty ? "Transaction failed (Code: $responseCode): $reason" : "Transaction failed: $reason";
if (onError != null) {
onError(errorMessage);
}
}
} else if (event["status"] == "error") {
_callbackFired = true;
final errorMessage = event["message"] ?? "An error occurred";
debugPrint("Error occurred in transaction: $errorMessage");
debugPrint("Full error event: $event");
if (onError != null) {
onError(errorMessage);
}
} else if (event["status"] == "event") {
// Intermediate SDK events – do NOT mark _callbackFired, just log
final eventMessage = event["message"] ?? "Event occurred";
debugPrint("Event occurred: $eventMessage");
}
} else if (event["status"] == "error") {
_callbackFired = true;
final errorMessage = event["message"] ?? "An error occurred";
debugPrint("Error occurred in transaction: $errorMessage");
debugPrint("Full error event: $event");
onError!(errorMessage);
} else if (event["status"] == "event") {
// Intermediate SDK events – do NOT mark _callbackFired, just log
final eventMessage = event["message"] ?? "Event occurred";
debugPrint("Event occurred: $eventMessage");
});
} catch (error) {
debugPrint("PayTabs Payment Error: $error");
LoaderBottomSheet.hideLoader();
if (onError != null) {
onError("Payment initialization failed: $error");
}
});
}
}
startAlternatePayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) {
startAlternatePayment(
{required String liveServiceID,
required String appointmentDate,
required String appointmentNo,
required String doctorID,
required String transactionID,
required String amount,
required String fileNumber,
required String serviceID,
required String projectID,
required bool isSchedule,
Function(PaytabsTransactionResponseModel)? onSuccess,
Function(String)? onError}) async {
// LoaderBottomSheet.showLoader();
TamaraRequestModel tamaraRequestModel = TamaraRequestModel();
tamaraRequestModel.merchantReference = transactionID;
tamaraRequestModel.merchantIdentifier = "Tamara";
tamaraRequestModel.clientRequestID = transactionID;
tamaraRequestModel.amount = num.parse(amount);
tamaraRequestModel.currency = "SR";
tamaraRequestModel.language = appState.isArabic() ? 'AR' : 'EN';
tamaraRequestModel.commandType = "PURCHASE";
tamaraRequestModel.customerEmail = "CustID_$fileNumber@HMG.com";
tamaraRequestModel.orderDescription = "Ancillary Order Payment";
tamaraRequestModel.isInstallment = true;
tamaraRequestModel.projectID = num.parse(projectID);
tamaraRequestModel.accessCode = getIt.get<AppState>().getAuthenticatedUser()!.mobileNumber!;
tamaraRequestModel.appointmentNo = appointmentNo;
tamaraRequestModel.customerName = "${getIt.get<AppState>().getAuthenticatedUser()!.firstName} ${getIt.get<AppState>().getAuthenticatedUser()!.lastName}";
tamaraRequestModel.fileNumber = fileNumber;
tamaraRequestModel.patientOutSA = getIt.get<AppState>().getAuthenticatedUser()!.outSa == 1 ? true : false;
tamaraRequestModel.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken);
tamaraRequestModel.latitude = appState.userLat.toString();
tamaraRequestModel.longitude = appState.userLong.toString();
tamaraRequestModel.serviceID = serviceID;
tamaraRequestModel.liveServiceID = liveServiceID;
tamaraRequestModel.doctorID = (doctorID != "") ? doctorID.toString() : "";
tamaraRequestModel.appointmentDate = appointmentDate;
tamaraRequestModel.isSchedule = isSchedule;
await getIt.get<PayfortViewModel>().tamaraRequestInsert(tamaraRequestModel: tamaraRequestModel);
LoaderBottomSheet.hideLoader();
bool _callbackFired = false; // prevent duplicate callback invocations
FlutterPaytabsBridge.startAlternativePaymentMethod(paymentConfiguration, (event) {
@ -260,4 +466,23 @@ class PayTabsViewModel extends ChangeNotifier {
}
});
}
Future<void> getPayTabsConfigurations({int? serviceId, int? projectId, int integrationId = 5, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await payfortRepo.getPayfortConfigurations(serviceId: serviceId, projectId: projectId, integrationId: integrationId);
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) {
paytabsProjectDetailsRespModel = apiResponse.data!;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
}

@ -78,6 +78,11 @@ class PrescriptionsViewModel extends ChangeNotifier {
notifyListeners();
}
void setIsPrescriptionDataNeedsReloading(bool value) {
isPrescriptionsDataNeedsReloading = value;
notifyListeners();
}
Future<bool> checkIfReminderExistForPrescription(int index) async {
final item = prescriptionDetailsList[index];
// Check device calendar
@ -191,7 +196,9 @@ class PrescriptionsViewModel extends ChangeNotifier {
result.fold(
// (failure) async => await errorHandlerService.handleError(failure: failure),
(failure) async {
onError!(failure.message);
if (onError != null) {
onError(failure.message);
}
},
(apiResponse) async {
if (apiResponse.messageStatus == 2) {

@ -70,9 +70,7 @@ class ProfilePictureViewModel extends ChangeNotifier {
try {
_cachedImageBytes = base64Decode(imageData);
_cachedImageDataHash = '${imageData.length}_${imageData.hashCode}';
print('✅ Cached existing profile image');
} catch (e) {
print('❌ Error caching existing image: $e');
_cachedImageBytes = null;
_cachedImageDataHash = null;
}
@ -85,7 +83,6 @@ class ProfilePictureViewModel extends ChangeNotifier {
final currentPatientId = _appState.getAuthenticatedUser()?.patientId;
if (currentPatientId != null && currentPatientId != _currentPatientId) {
print('🔄 User switched detected: $_currentPatientId -> $currentPatientId');
_handleUserSwitch(currentPatientId);
return true;
}
@ -97,8 +94,6 @@ class ProfilePictureViewModel extends ChangeNotifier {
final oldPatientId = _currentPatientId;
_currentPatientId = newPatientId;
print('🧹 Clearing cache for old user: $oldPatientId');
// Clear AppState cache
_appState.clearProfileImageCache();
@ -113,17 +108,14 @@ class ProfilePictureViewModel extends ChangeNotifier {
notifyListeners();
// Load new user's profile image
print('📥 Loading profile image for new user: $newPatientId');
_profileSettingsViewModel.getProfileImage(
patientID: newPatientId,
forceRefresh: true,
onSuccess: (data) {
print('✅ Profile image loaded successfully for user: $newPatientId');
_tryCacheExistingImage();
notifyListeners();
},
onError: (error) {
print('❌ Error loading profile image: $error');
notifyListeners();
},
);
@ -133,22 +125,18 @@ class ProfilePictureViewModel extends ChangeNotifier {
void loadProfileImage({bool forceRefresh = false}) {
// Check if profile image is already loaded in AppState (skip if forcing refresh)
if (!forceRefresh && _appState.getProfileImageData != null && _appState.getProfileImageData!.isNotEmpty) {
print('✅ Profile image already cached in AppState');
return;
}
final patientID = _appState.getAuthenticatedUser()?.patientId;
if (patientID == null) {
print('⚠️ Cannot load profile image - no authenticated user');
return;
}
print('📥 Loading profile image for patient: $patientID (forceRefresh: $forceRefresh)');
_profileSettingsViewModel.getProfileImage(
patientID: patientID,
forceRefresh: forceRefresh,
onSuccess: (data) {
print('✅ Profile image loaded successfully');
_tryCacheExistingImage();
notifyListeners();
},
@ -188,10 +176,6 @@ class ProfilePictureViewModel extends ChangeNotifier {
false, // Don't show files option, only camera and gallery
(base64String, file) async {
try {
print('=== Starting image processing ===');
print('File path: ${file.path}');
print('File exists: ${await file.exists()}');
print('Original file size: ${await file.length() / 1024} KB');
// Compress and resize the image
print('Calling compressAndResizeImage...');
@ -266,14 +250,10 @@ class ProfilePictureViewModel extends ChangeNotifier {
onError('No authenticated user found');
return;
}
print('📤 Uploading profile image for patient: $patientID');
_profileSettingsViewModel.uploadProfileImage(
patientID: patientID,
imageData: base64String,
onSuccess: (data) async {
print('✅ Profile image uploaded successfully');
// Clear old cache first to ensure fresh data
_cachedImageBytes = null;
_cachedImageDataHash = null;
@ -287,9 +267,13 @@ class ProfilePictureViewModel extends ChangeNotifier {
// Increment version to trigger targeted rebuild (no full screen refresh)
_profileImageVersion.value++;
print('🔄 Profile image version updated to ${_profileImageVersion.value} (targeted rebuild)');
onSuccess(data);
final String successMessage = data is String
? data
: (data is Map && data['message'] != null
? data['message'].toString()
: data?.toString() ?? 'Success');
onSuccess(successMessage);
},
onError: (error) {
print('❌ Error uploading profile image: $error');
@ -308,7 +292,6 @@ class ProfilePictureViewModel extends ChangeNotifier {
try {
_cachedImageBytes = base64Decode(imageData!);
_cachedImageDataHash = currentHash;
print('🔄 Updated cached image bytes');
} catch (e) {
print('❌ Error decoding profile image: $e');
_cachedImageBytes = null;
@ -326,7 +309,6 @@ class ProfilePictureViewModel extends ChangeNotifier {
_cachedImageDataHash = null;
_selectedImage = null;
notifyListeners();
print('🧹 Cleared all profile picture cache');
}
/// Check if we should show shimmer loading

@ -1948,5 +1948,89 @@ abstract class LocaleKeys {
static const weeklyShift = 'weeklyShift';
static const dayOff = 'dayOff';
static const hide = 'hide';
static const viewPatientQR = 'viewPatientQR';
static const patientQR = 'patientQR';
static const forText = 'forText';
static const liveCareTermsHeading = 'liveCareTermsHeading';
static const liveCareTermsIntroHeading = 'liveCareTermsIntroHeading';
static const liveCareTermsConditions1 = 'liveCareTermsConditions1';
static const liveCareTermsConditions2 = 'liveCareTermsConditions2';
static const liveCareTermsHeading2 = 'liveCareTermsHeading2';
static const liveCareTermsConditions3 = 'liveCareTermsConditions3';
static const liveCareTermsConditions3a = 'liveCareTermsConditions3a';
static const liveCareTermsConditions3b = 'liveCareTermsConditions3b';
static const liveCareTermsConditions3c = 'liveCareTermsConditions3c';
static const liveCareTermsConditions3d = 'liveCareTermsConditions3d';
static const liveCareTermsConditions4 = 'liveCareTermsConditions4';
static const liveCareTermsConditions5 = 'liveCareTermsConditions5';
static const liveCareTermsConditions6 = 'liveCareTermsConditions6';
static const liveCareTermsHeading3 = 'liveCareTermsHeading3';
static const liveCareTermsConditions7 = 'liveCareTermsConditions7';
static const liveCareTermsConditions8 = 'liveCareTermsConditions8';
static const liveCareTermsConditions9 = 'liveCareTermsConditions9';
static const liveCareTermsConditions10 = 'liveCareTermsConditions10';
static const liveCareTermsConditions11 = 'liveCareTermsConditions11';
static const liveCareTermsConditions12 = 'liveCareTermsConditions12';
static const liveCareTermsConditions13 = 'liveCareTermsConditions13';
static const liveCareTermsHeading4 = 'liveCareTermsHeading4';
static const liveCareTermsConditions14 = 'liveCareTermsConditions14';
static const liveCareTermsConditions15 = 'liveCareTermsConditions15';
static const liveCareTermsConditions16 = 'liveCareTermsConditions16';
static const liveCareTermsHeading5 = 'liveCareTermsHeading5';
static const liveCareTermsConditions17 = 'liveCareTermsConditions17';
static const liveCareTermsConditions18 = 'liveCareTermsConditions18';
static const liveCareTermsConditions19 = 'liveCareTermsConditions19';
static const liveCareTermsConditions19a = 'liveCareTermsConditions19a';
static const liveCareTermsConditions19b = 'liveCareTermsConditions19b';
static const liveCareTermsConditions19c = 'liveCareTermsConditions19c';
static const liveCareTermsConditions19d = 'liveCareTermsConditions19d';
static const liveCareTermsConditions19e = 'liveCareTermsConditions19e';
static const liveCareTermsConditions20 = 'liveCareTermsConditions20';
static const liveCareTermsHeading6 = 'liveCareTermsHeading6';
static const liveCareTermsConditions21 = 'liveCareTermsConditions21';
static const liveCareTermsConditions22 = 'liveCareTermsConditions22';
static const liveCareTermsConditions23 = 'liveCareTermsConditions23';
static const liveCareTermsHeading7 = 'liveCareTermsHeading7';
static const liveCareTermsConditions24 = 'liveCareTermsConditions24';
static const liveCareTermsConditions25 = 'liveCareTermsConditions25';
static const liveCareTermsConditions26 = 'liveCareTermsConditions26';
static const liveCareTermsConditions27 = 'liveCareTermsConditions27';
static const liveCareTermsConditions28 = 'liveCareTermsConditions28';
static const liveCareTermsConditions29 = 'liveCareTermsConditions29';
static const liveCareTermsConditions30 = 'liveCareTermsConditions30';
static const liveCareTermsHeading8 = 'liveCareTermsHeading8';
static const liveCareTermsConditions31 = 'liveCareTermsConditions31';
static const liveCareTermsHeading9 = 'liveCareTermsHeading9';
static const liveCareTermsConditions32 = 'liveCareTermsConditions32';
static const liveCareTermsConditions33 = 'liveCareTermsConditions33';
static const liveCareTermsConditions34 = 'liveCareTermsConditions34';
static const liveCareTermsConditions35 = 'liveCareTermsConditions35';
static const liveCareTermsHeading10 = 'liveCareTermsHeading10';
static const liveCareTermsConditions36 = 'liveCareTermsConditions36';
static const liveCareTermsHeading11 = 'liveCareTermsHeading11';
static const liveCareTermsConditions37 = 'liveCareTermsConditions37';
static const liveCareTermsConditions38 = 'liveCareTermsConditions38';
static const liveCareTermsHeading12 = 'liveCareTermsHeading12';
static const liveCareTermsConditions39 = 'liveCareTermsConditions39';
static const liveCareTermsConditions40 = 'liveCareTermsConditions40';
static const liveCareTermsConditions41 = 'liveCareTermsConditions41';
static const liveCareTermsHeading13 = 'liveCareTermsHeading13';
static const liveCareTermsConditions42 = 'liveCareTermsConditions42';
static const liveCareTermsHeading14 = 'liveCareTermsHeading14';
static const liveCareTermsConditions43 = 'liveCareTermsConditions43';
static const liveCareTermsConditions43a = 'liveCareTermsConditions43a';
static const liveCareTermsConditions43b = 'liveCareTermsConditions43b';
static const liveCareTermsConditions43c = 'liveCareTermsConditions43c';
static const liveCareTermsConditions43d = 'liveCareTermsConditions43d';
static const liveCareTermsConditions43e = 'liveCareTermsConditions43e';
static const liveCareTermsConditions43f = 'liveCareTermsConditions43f';
static const liveCareTermsConditions43g = 'liveCareTermsConditions43g';
static const liveCareTermsHeading15 = 'liveCareTermsHeading15';
static const liveCareTermsConditions44 = 'liveCareTermsConditions44';
static const liveCareTermsConditions45 = 'liveCareTermsConditions45';
static const liveCareTermsConditions46 = 'liveCareTermsConditions46';
static const liveCareTermsHeading16 = 'liveCareTermsHeading16';
static const liveCareTermsConditions47 = 'liveCareTermsConditions47';
static const liveCareTermsConditions48 = 'liveCareTermsConditions48';
}

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
@ -7,8 +8,11 @@ import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.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/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/active_prescriptions/active_prescriptions_view_model.dart';
import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart';
@ -32,7 +36,9 @@ import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_
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';
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart';
import 'package:hmg_patient_app_new/features/myfatoorah/myfatoorah_view_model.dart';
import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart';
import 'package:hmg_patient_app_new/features/occasion_theme/occasion_theme_view_model.dart';
import 'package:hmg_patient_app_new/features/offers_and_discounts/offers_and_discounts_view_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart';
import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart';
@ -53,11 +59,15 @@ import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/app_lifecycle_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/services/security_service.dart';
import 'package:hmg_patient_app_new/theme/app_theme.dart';
import 'package:hmg_patient_app_new/unsafe_device.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart' show DateRangeSelectorRangeViewModel;
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';
import 'package:safe_device/safe_device.dart';
import 'package:safe_device/safe_device_config.dart';
import 'core/utils/size_utils.dart';
import 'features/monthly_reports/terms_conditions_view_model.dart';
@ -110,6 +120,12 @@ Future<void> callInitializations() async {
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
Intl.defaultLocale = "en_US";
SafeDevice.init(
SafeDeviceConfig(mockLocationCheckEnabled: false), // disables mock location check on Android
);
try {
// Attempt to get the default app. If it exists, this avoids the error.
await Firebase.app();
@ -125,6 +141,26 @@ Future<void> callInitializations() async {
HttpOverrides.global = MyHttpOverrides();
await callAppStateInitializations();
// Initialize Security Service early to catch threats before app logic runs
if (kReleaseMode) {
await getIt.get<SecurityService>().initialize();
}
// Set up critical threat callback to navigate to unsafe device page
getIt.get<SecurityService>().setOnCriticalThreatCallback(() {
final navigationService = getIt.get<NavigationService>();
final context = navigationService.navigatorKey.currentContext;
if (context != null) {
// Clear entire navigation stack and show unsafe device page
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (_) => const UnsafeDevice()),
(route) => false, // Remove all previous routes
);
getIt.get<LoggerService>().logError('🚨 Navigated to UnsafeDevice page - Critical threat detected');
}
});
// Initialize App Lifecycle Service to monitor background/foreground transitions
getIt.get<AppLifecycleService>().initialize();
@ -133,6 +169,14 @@ Future<void> callInitializations() async {
// Restore persisted accessibility settings before the first frame.
getIt.get<ProfileSettingsViewModel>().loadAccessibilitySettings();
// Restore last-known occasion theme instantly (offline-safe, synchronous)
// so the very first frame is already correct, then let the Remote Config
// fetch happen in the background without blocking app startup. Fully
// disabled by default — the app looks exactly as it does today until
// Remote Config enables an occasion (e.g. National Day) on the server side.
final occasionThemeViewModel = getIt.get<OccasionThemeViewModel>();
unawaited(occasionThemeViewModel.initialize());
}
void main() async {
@ -270,9 +314,15 @@ void main() async {
ChangeNotifierProvider<PayTabsViewModel>(
create: (_) => getIt.get<PayTabsViewModel>(),
),
ChangeNotifierProvider<MyFatoorahViewModel>(
create: (_) => getIt.get<MyFatoorahViewModel>(),
),
ChangeNotifierProvider<RefundRequestViewModel>(
create: (_) => getIt.get<RefundRequestViewModel>(),
)
),
ChangeNotifierProvider<OccasionThemeViewModel>(
create: (_) => getIt.get<OccasionThemeViewModel>(),
),
], child: MyApp()),
), // Wrap your app
);
@ -340,11 +390,11 @@ class MyApp extends StatelessWidget {
return Sizer(
builder: (context, orientation, deviceType) {
// SizeConfig().init(constraints, orientation);
return Consumer<ProfileSettingsViewModel>(
builder: (context, profileVm, _) {
return Consumer2<ProfileSettingsViewModel, OccasionThemeViewModel>(
builder: (context, profileVm, occasionThemeVm, _) {
final isArabic = EasyLocalization.of(context)?.locale.languageCode == "ar";
return MaterialApp(
key: ValueKey('app_theme_${profileVm.isDarkMode}'),
key: ValueKey('app_theme_${profileVm.isDarkMode}_${occasionThemeVm.isOccasionActive}_${occasionThemeVm.config.key}'),
// title: 'Dr. AlHabib',
title: 'Dr. AlHabib Beta',
builder: (context, mchild) {
@ -370,6 +420,7 @@ class MyApp extends StatelessWidget {
darkTheme: AppTheme.getDarkTheme(isArabic),
themeMode: profileVm.isDarkMode ? ThemeMode.dark : ThemeMode.light,
navigatorKey: getIt.get<NavigationService>().navigatorKey,
navigatorObservers: [getIt.get<NavigationService>().routeObserver],
);
},
);

@ -22,6 +22,9 @@ import 'package:hmg_patient_app_new/features/book_appointments/models/resp_model
import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart';
import 'package:hmg_patient_app_new/features/contact_us/models/feedback_type.dart';
import 'package:hmg_patient_app_new/features/hospital/AppPermission.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/vital_sign_respo_model.dart';
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
@ -40,6 +43,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appoin
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/bell_switch.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart';
import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart';
import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_page.dart';
import 'package:hmg_patient_app_new/services/clarity_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
@ -666,6 +670,76 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
padding: EdgeInsets.zero,
shrinkWrap: true,
children: [
MedicalFileCard(
label: LocaleKeys.vitalSigns.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.vital_sign_item_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context));
final hmgServicesVM = getIt.get<HmgServicesViewModel>();
final appt = widget.patientAppointmentHistoryResponseModel;
final targetTransNo = appt.vitalSignAppointmentNo ?? appt.appointmentNo;
await hmgServicesVM.getPatientVitalSign(
transNo: targetTransNo,
projectID: appt.projectID,
onSuccess: (apiResponse) {
LoaderBottomSheet.hideLoader();
final List<VitalSignResModel> vitalList = (apiResponse is GenericApiModel<List<VitalSignResModel>>)
? (apiResponse.data ?? [])
: (apiResponse.data is List<VitalSignResModel>
? (apiResponse.data as List<VitalSignResModel>)
: <VitalSignResModel>[]);
VitalSignResModel? matchedVitalSign;
if (vitalList.isNotEmpty) {
// 1. Match by vitalSignAppointmentNo or appointmentNo
if (targetTransNo != null) {
final targetStr = targetTransNo.toString();
for (final v in vitalList) {
if (v.transNo != null && v.transNo.toString() == targetStr) {
matchedVitalSign = v;
break;
}
}
}
// 2. If specific API returned records for this TransNo/ProjectID, fallback to first
matchedVitalSign ??= vitalList.first;
Navigator.of(context).push(
CustomPageRoute(
page: VitalSignPage(
specificVitalSign: matchedVitalSign,
isFromAppointment: true,
),
),
);
} else {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: LocaleKeys.noVitalSignsRecordedYet.tr(context: context)),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
);
}),
MedicalFileCard(
label: LocaleKeys.labResults.tr(context: context),
textColor: AppColors.blackColor,
@ -790,6 +864,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
// ),
// );
}),
MedicalFileCard(
label: LocaleKeys.doctorResponses.tr(context: context),
textColor: AppColors.blackColor,
@ -914,7 +989,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
openDoctorScheduleCalendar();
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
borderColor: Colors.transparent,
textColor: Colors.white,
fontSize: 16.f,
fontWeight: FontWeight.w600,
@ -945,6 +1020,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
);
})
: SizedBox.shrink(),
],
);
}),
@ -1021,7 +1097,7 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
openDoctorScheduleCalendar();
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
borderColor: Colors.transparent,
textColor: Colors.white,
fontSize: 16.f,
fontWeight: FontWeight.w600,
@ -1200,8 +1276,8 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
onSuccess: (apiResponse) {
LoaderBottomSheet.hideLoader();
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.getPatientAppointments(true, false);
// myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
// myAppointmentsViewModel.getPatientAppointments(true, false);
showCommonBottomSheet(context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentConfirmedSuccessfully.tr(context: context)), callBackFunc: (str) {
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);

@ -69,12 +69,18 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
widget.patientAppointmentHistoryResponseModel.clinicID,
widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false, onSuccess: (val) {
myAppointmentsViewModel.getTamaraInstallmentsDetails().then((val) {
if (myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel != null) {
// if (myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel != null && myAppointmentsViewModel.patientAppointmentShareResponseModel != null) {
if (myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel != null &&
myAppointmentsViewModel.patientAppointmentShareResponseModel?.patientShareWithTax != null &&
myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel?.minLimit?.amount != null &&
myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel?.maxLimit?.amount != null) {
if (myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! &&
myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) {
setState(() {
isShowTamara = true;
});
if (mounted) {
setState(() {
isShowTamara = true;
});
}
}
}
});
@ -139,12 +145,31 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "MADA";
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",
num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
transID = Utils.getAppointmentTransID(
widget.patientAppointmentHistoryResponseModel.projectID,
widget.patientAppointmentHistoryResponseModel.clinicID,
widget.patientAppointmentHistoryResponseModel.appointmentNo,
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
await paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",
num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(),
widget.patientAppointmentHistoryResponseModel.projectID);
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: "0",
isScheduleLiveCare: widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? "true" : "false",
appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate!,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
doctorID: widget.patientAppointmentHistoryResponseModel.doctorID.toString(),
transactionID: transID,
amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(),
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
orderDescription: "Appointment Payment",
serviceID: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum().toString(),
projectID: widget.patientAppointmentHistoryResponseModel.projectID.toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
await myAppointmentsViewModel.createAdvancePayment(
paymentMethodName: selectedPaymentMethod,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
@ -155,7 +180,6 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
patientID: appState.getAuthenticatedUser()!.patientId.toString(),
patientType: appState.getAuthenticatedUser()!.patientType!,
onSuccess: (value) async {
print(value);
await myAppointmentsViewModel.addAdvanceNumberRequest(
advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID)
? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString()
@ -239,7 +263,6 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
orderDescription: "Appointment Payment",
amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(),
onSucceeded: (paymentResult) async {
print("Payment Success Data: ${paymentResult.toString()}");
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
await myAppointmentsViewModel.createAdvancePayment(
paymentMethodName: selectedPaymentMethod,
@ -251,7 +274,6 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
patientID: appState.getAuthenticatedUser()!.patientId.toString(),
patientType: appState.getAuthenticatedUser()!.patientType!,
onSuccess: (value) async {
print(value);
await myAppointmentsViewModel.addAdvanceNumberRequest(
advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID)
? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString()
@ -362,12 +384,31 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "VISA";
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",
num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
transID = Utils.getAppointmentTransID(
widget.patientAppointmentHistoryResponseModel.projectID,
widget.patientAppointmentHistoryResponseModel.clinicID,
widget.patientAppointmentHistoryResponseModel.appointmentNo,
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
await paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",
num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(),
widget.patientAppointmentHistoryResponseModel.projectID);
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: "0",
isScheduleLiveCare: widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? "true" : "false",
appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate!,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
doctorID: widget.patientAppointmentHistoryResponseModel.doctorID.toString(),
transactionID: transID,
amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(),
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
orderDescription: "Appointment Payment",
serviceID: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum().toString(),
projectID: widget.patientAppointmentHistoryResponseModel.projectID.toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
await myAppointmentsViewModel.createAdvancePayment(
paymentMethodName: selectedPaymentMethod,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
@ -378,7 +419,6 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
patientID: appState.getAuthenticatedUser()!.patientId.toString(),
patientType: appState.getAuthenticatedUser()!.patientType!,
onSuccess: (value) async {
print(value);
await myAppointmentsViewModel.addAdvanceNumberRequest(
advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID)
? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString()
@ -462,7 +502,6 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(),
orderDescription: "Appointment Payment",
onSucceeded: (paymentResult) async {
print("Payment Success Data: ${paymentResult.toString()}");
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
await myAppointmentsViewModel.createAdvancePayment(
paymentMethodName: selectedPaymentMethod,
@ -474,7 +513,6 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
patientID: appState.getAuthenticatedUser()!.patientId.toString(),
patientType: appState.getAuthenticatedUser()!.patientType!,
onSuccess: (value) async {
print(value);
await myAppointmentsViewModel.addAdvanceNumberRequest(
advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID)
? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString()
@ -590,96 +628,94 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "TAMARA";
transID = Utils.getAppointmentTransID(
widget.patientAppointmentHistoryResponseModel.projectID,
widget.patientAppointmentHistoryResponseModel.clinicID,
widget.patientAppointmentHistoryResponseModel.appointmentNo,
);
if (appState.isPaytabsEnabled) {
paytabsViewModel.setAlternatePaymentConfiguration(
"Appointment Payment", num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()));
paytabsViewModel.startAlternatePayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
tamaraOrderID = transactionData.cartID!;
transID = transactionData.transactionReference!;
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
await payfortViewModel.updateTamaraRequestStatus(responseMessage: "success", status: "14", clientRequestID: transID, tamaraOrderID: tamaraOrderID);
await payfortViewModel.markAppointmentAsTamaraPaid(
"Appointment Payment",
num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(),
widget.patientAppointmentHistoryResponseModel.projectID);
paytabsViewModel.startAlternatePayment(
liveServiceID: "0",
appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate!,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
doctorID: widget.patientAppointmentHistoryResponseModel.doctorID.toString(),
transactionID: transID,
amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(),
fileNumber: widget.patientAppointmentHistoryResponseModel.patientID.toString(),
serviceID: "2",
projectID: widget.patientAppointmentHistoryResponseModel.projectID.toString(),
isSchedule: widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false,
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
tamaraOrderID = transactionData.cartID!;
// transID = transactionData.transactionReference!;
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
await payfortViewModel.updateTamaraRequestStatus(responseMessage: "success", status: "14", clientRequestID: transID, tamaraOrderID: tamaraOrderID);
await payfortViewModel.markAppointmentAsTamaraPaid(
projectID: widget.patientAppointmentHistoryResponseModel.projectID, appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo);
await myAppointmentsViewModel.createAdvancePayment(
paymentMethodName: selectedPaymentMethod,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
payedAmount: num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
paymentReference: transactionData.transactionReference!,
patientID: appState.getAuthenticatedUser()!.patientId.toString(),
patientType: appState.getAuthenticatedUser()!.patientType!,
onSuccess: (value) async {
print(value);
await myAppointmentsViewModel.addAdvanceNumberRequest(
advanceNumber: "Tamara-Advance-0000",
paymentReference: transactionData.transactionReference!,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
onSuccess: (value) async {
if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) {
//TODO: Implement LiveCare Check-In API Call
await myAppointmentsViewModel.insertLiveCareVIDARequest(
clientRequestID: transID,
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
onSuccess: (apiResponse) {
Future.delayed(Duration(milliseconds: 500), () {
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
LoaderBottomSheet.hideLoader();
await myAppointmentsViewModel.addAdvanceNumberRequest(
advanceNumber: "Tamara-Advance-0000",
paymentReference: transactionData.transactionReference!,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
onSuccess: (value) async {
if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) {
//TODO: Implement LiveCare Check-In API Call
await myAppointmentsViewModel.insertLiveCareVIDARequest(
clientRequestID: transID,
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
onSuccess: (apiResponse) {
Future.delayed(Duration(milliseconds: 500), () {
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
LoaderBottomSheet.hideLoader();
getIt.get<HabibWalletViewModel>().setIsWalletAmountToBeLoaded(true);
getIt.get<HabibWalletViewModel>().initHabibWalletProvider();
getIt.get<HabibWalletViewModel>().getPatientBalanceAmount();
getIt.get<HabibWalletViewModel>().setIsWalletAmountToBeLoaded(true);
getIt.get<HabibWalletViewModel>().initHabibWalletProvider();
getIt.get<HabibWalletViewModel>().getPatientBalanceAmount();
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
});
},
onError: (error) {});
} else {
await myAppointmentsViewModel.generateAppointmentQR(
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!,
onSuccess: (apiResponse) {
Future.delayed(Duration(milliseconds: 500), () {
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
});
},
onError: (error) {});
} else {
await myAppointmentsViewModel.generateAppointmentQR(
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!,
onSuccess: (apiResponse) {
Future.delayed(Duration(milliseconds: 500), () {
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
getIt.get<HabibWalletViewModel>().setIsWalletAmountToBeLoaded(true);
getIt.get<HabibWalletViewModel>().initHabibWalletProvider();
getIt.get<HabibWalletViewModel>().getPatientBalanceAmount();
getIt.get<HabibWalletViewModel>().setIsWalletAmountToBeLoaded(true);
getIt.get<HabibWalletViewModel>().initHabibWalletProvider();
getIt.get<HabibWalletViewModel>().getPatientBalanceAmount();
LoaderBottomSheet.hideLoader();
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
});
LoaderBottomSheet.hideLoader();
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
});
}
});
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
}, onError: (err) async {
});
}
});
}, onError: (err) async {
await payfortViewModel.updateTamaraRequestStatus(responseMessage: "Failed", status: "00", clientRequestID: transID, tamaraOrderID: tamaraOrderID);
showCommonBottomSheetWithoutHeight(
context,
@ -690,7 +726,7 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
);
});
} else {
openPaymentURL("tamara");
openPaymentURL("TAMARA");
}
})
: SizedBox.shrink(),
@ -1084,10 +1120,13 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
ApplePayInsertRequest applePayInsertRequest = ApplePayInsertRequest();
await payfortViewModel.getPayfortConfigurations(
serviceId: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(),
projectId: widget.patientAppointmentHistoryResponseModel.projectID,
integrationId: 2);
if (!appState.isPaytabsEnabled) {
await payfortViewModel.getPayfortConfigurations(
serviceId: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), projectId: widget.patientAppointmentHistoryResponseModel.projectID, integrationId: 2);
} else {
await paytabsViewModel.setPaymentConfiguration("Appointment Payment", num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), widget.patientAppointmentHistoryResponseModel.projectID);
}
applePayInsertRequest.clientRequestID = transID;
applePayInsertRequest.clinicID = widget.patientAppointmentHistoryResponseModel.clinicID;
@ -1123,12 +1162,13 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
applePayInsertRequest.isMobSDK = true;
applePayInsertRequest.merchantReference = transID;
applePayInsertRequest.merchantIdentifier = payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier;
applePayInsertRequest.merchantIdentifier =
appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.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.signature = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.signature : payfortViewModel.payfortProjectDetailsRespModel!.signature;
applePayInsertRequest.accessCode = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.accessCode : payfortViewModel.payfortProjectDetailsRespModel!.accessCode;
applePayInsertRequest.shaRequestPhrase = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.shaRequest : payfortViewModel.payfortProjectDetailsRespModel!.shaRequest;
applePayInsertRequest.shaResponsePhrase = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.shaResponse : payfortViewModel.payfortProjectDetailsRespModel!.shaResponse;
applePayInsertRequest.returnURL = "";
//TODO: Need to pass dynamic params to the Apple Pay instead of static values
@ -1136,10 +1176,6 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
if (appState.isPaytabsEnabled) {
selectedPaymentMethod = "VISA";
LoaderBottomSheet.hideLoader();
paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",
num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
);
paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
await myAppointmentsViewModel.createAdvancePayment(

@ -45,8 +45,9 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
scheduleMicrotask(() {
if (!myAppointmentsViewModel.isMyAppointmentsLoading) {
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
myAppointmentsViewModel.getPatientAppointments(true, false, isForTimeLine: false);
}
// myAppointmentsViewModel.changeTabToArrived();
});
super.initState();
}
@ -66,12 +67,12 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
SizedBox(height: 16.h),
CustomTabBar(
initialIndex: myAppointmentsVM.selectedTabIndex,
activeTextColor: Color(0xffED1C2B),
activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
activeTextColor: AppColors.primaryRedColor,
activeBackgroundColor: AppColors.secondaryLightRedColor,
tabs: [
CustomTabBarModel(null, LocaleKeys.allAppt.tr(context: context)),
// CustomTabBarModel(null, LocaleKeys.allAppt.tr(context: context)),
CustomTabBarModel(null, LocaleKeys.upcoming.tr(context: context)),
CustomTabBarModel(null, LocaleKeys.completed.tr(context: context)),
CustomTabBarModel(null, LocaleKeys.arrived.tr(context: context)),
],
onTabChange: (index) {
setState(() {
@ -80,6 +81,10 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
myAppointmentsViewModel.onTabChange(index);
myAppointmentsViewModel.updateListWRTTab(index);
context.read<DateRangeSelectorRangeViewModel>().flush();
if(index == 1) {
myAppointmentsViewModel.loadFullArrivedAppointmentsIfNeeded();
}
},
).paddingSymmetrical(24.h, 0.h),
// Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
@ -150,7 +155,7 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
? myAppointmentsVM.patientAppointmentsViewList.length
: 1,
itemBuilder: (context, index) {
final isExpanded = myAppointmentsVM.selectedTabIndex == 1 ? true : expandedIndex == index;
final isExpanded = myAppointmentsVM.selectedTabIndex == 0 ? true : expandedIndex == index;
return myAppointmentsVM.isMyAppointmentsLoading
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
@ -264,9 +269,9 @@ class _MyAppointmentsPageState extends State<MyAppointmentsPage> {
),
);
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
isBold: true,
borderRadius: 12.r,

@ -14,6 +14,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart';
import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
@ -231,7 +232,7 @@ class _AppointmentCardState extends State<AppointmentCard> {
Utils.buildImgWithNetwork(
url: widget.isLoading
? 'https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png'
: widget.patientAppointmentHistoryResponseModel.doctorImageURL!,
: widget.patientAppointmentHistoryResponseModel.doctorImageURL,
iconColor: AppColors.transparent,
width: 63.h,
height: 63.h,
@ -320,7 +321,7 @@ class _AppointmentCardState extends State<AppointmentCard> {
icon: AppAssets.appointment_calendar_icon,
richText: widget.isLoading
? 'Cardiology'.toText10().toShimmer2(isShow: widget.isLoading)
: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), false)}"
: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), false)} ${(widget.patientAppointmentHistoryResponseModel.startTime != null && widget.patientAppointmentHistoryResponseModel.startTime!.isNotEmpty) ? widget.patientAppointmentHistoryResponseModel.startTime!.substring(0, 5) : DateUtil.formatDateToTimeLang(DateUtil.convertStringToDateSaudiTimezone(widget.patientAppointmentHistoryResponseModel.appointmentDate!, widget.patientAppointmentHistoryResponseModel.projectID), false)}"
.toText10(isEnglishOnly: true, isBold: true),
).toShimmer2(isShow: widget.isLoading),
),
@ -636,6 +637,7 @@ class _AppointmentCardState extends State<AppointmentCard> {
}
void _goToDetails(BuildContext context) {
if (widget.isLoading) return;
if (widget.isFromMedicalReport) return;
if (widget.isForEyeMeasurements) {
Navigator.of(context).push(
@ -677,7 +679,15 @@ class _AppointmentCardState extends State<AppointmentCard> {
projectName: widget.patientAppointmentHistoryResponseModel.projectName,
);
final clinic = GetClinicsListResponseModel(
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
clinicDescription: widget.patientAppointmentHistoryResponseModel.clinicName,
clinicDescriptionN: widget.patientAppointmentHistoryResponseModel.clinicName,
);
widget.bookAppointmentsViewModel.setSelectedDoctor(doctor);
widget.bookAppointmentsViewModel.setSelectedClinic(clinic);
LoaderBottomSheet.showLoader();
await widget.bookAppointmentsViewModel.getDoctorFreeSlots(

@ -45,8 +45,9 @@ class AppointmentCheckinBottomSheet extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
checkInOptionCard(
AppAssets.checkin_location_icon,
Utils.havePrivilege(102)
? checkInOptionCard(
AppAssets.checkin_location_icon,
LocaleKeys.liveLocation.tr(context: context),
LocaleKeys.verifyYourLocationAtHospital.tr(context: context),
).onPress(() {
@ -71,9 +72,10 @@ class AppointmentCheckinBottomSheet extends StatelessWidget {
}, isFullScreen: false);
}
});
}),
})
: SizedBox.shrink(),
// SizedBox(height: 16.h),
// checkInOptionCard(O
// checkInOptionCard(
// AppAssets.checkin_nfc_icon,
// LocaleKeys.nfcNearFieldCommunication.tr(context: context),
// LocaleKeys.scanPhoneViaNFC.tr(context: context),

@ -84,8 +84,7 @@ class AppointmentDoctorCard extends StatelessWidget {
children: [
Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.h, height: 15.h, iconColor: AppColors.ratingColorYellow),
SizedBox(height: 2.h),
"${patientAppointmentHistoryResponseModel.decimalDoctorRate ?? 0.0}"
.toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true),
"${patientAppointmentHistoryResponseModel.decimalDoctorRate ?? 0.0}".toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true),
],
),
).circle(100),
@ -102,11 +101,7 @@ class AppointmentDoctorCard extends StatelessWidget {
children: [
Expanded(
child: (patientAppointmentHistoryResponseModel.doctorNameObj ?? "").toText16(
isBold: true,
isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? ""),
textOverflow: TextOverflow.ellipsis,
maxlines: 2
),
isBold: true, isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? ""), textOverflow: TextOverflow.ellipsis, maxlines: 2),
),
SizedBox(width: 12.w),
// (patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null &&
@ -127,9 +122,9 @@ class AppointmentDoctorCard extends StatelessWidget {
runSpacing: 6.h,
children: [
AppCustomChipWidget(
labelText: (patientAppointmentHistoryResponseModel.clinicName!.length > 20
? '${patientAppointmentHistoryResponseModel.clinicName!.substring(0, 20)}...'
: patientAppointmentHistoryResponseModel.clinicName!),
labelText: ((patientAppointmentHistoryResponseModel.clinicName ?? "").length > 20
? '${(patientAppointmentHistoryResponseModel.clinicName ?? "").substring(0, 20)}...'
: (patientAppointmentHistoryResponseModel.clinicName ?? "")),
labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w),
),
AppCustomChipWidget(
@ -142,26 +137,19 @@ class AppointmentDoctorCard extends StatelessWidget {
child: AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w),
icon: AppAssets.doctor_calendar_icon,
richText: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(
DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate),
false,
)}"
.toText10(isBold: true),
richText:
"${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${(patientAppointmentHistoryResponseModel.startTime != null && patientAppointmentHistoryResponseModel.startTime!.isNotEmpty) ? patientAppointmentHistoryResponseModel.startTime!.substring(0, 5) : DateUtil.formatDateToTimeLang(DateUtil.convertStringToDateSaudiTimezone(patientAppointmentHistoryResponseModel.appointmentDate!, patientAppointmentHistoryResponseModel.projectID), false)}"
.toText10(isBold: true),
),
),
),
AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w),
icon: !patientAppointmentHistoryResponseModel.isLiveCareAppointment!
? AppAssets.walkin_appointment_icon
: AppAssets.small_livecare_icon,
iconColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : Colors.white,
labelText: patientAppointmentHistoryResponseModel.isLiveCareAppointment!
? LocaleKeys.livecare.tr(context: context)
: LocaleKeys.walkin.tr(context: context),
backgroundColor:
!patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.greyColor : AppColors.successColor,
textColor: !patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? AppColors.textColor : Colors.white,
icon: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? AppAssets.walkin_appointment_icon : AppAssets.small_livecare_icon,
iconColor: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? AppColors.textColor : Colors.white,
labelText: (patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? LocaleKeys.livecare.tr(context: context) : LocaleKeys.walkin.tr(context: context),
backgroundColor: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? AppColors.greyColor : AppColors.successColor,
textColor: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) ? AppColors.textColor : Colors.white,
),
],
),
@ -169,46 +157,45 @@ class AppointmentDoctorCard extends StatelessWidget {
),
),
(patientAppointmentHistoryResponseModel.isLiveCareAppointment! ||
patientAppointmentHistoryResponseModel.isExecludeDoctor! ||
patientAppointmentHistoryResponseModel.isExecludeDoctor! ||
patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! == false ||
patientAppointmentHistoryResponseModel.isActiveDoctor! == false ||
!Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID))
!Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID))
? SizedBox.shrink()
: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown)
.onPress(() async {
DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel();
selectedDoctor.doctorID = patientAppointmentHistoryResponseModel.doctorID;
selectedDoctor.doctorImageURL = patientAppointmentHistoryResponseModel.doctorImageURL;
selectedDoctor.name = patientAppointmentHistoryResponseModel.doctorNameObj;
selectedDoctor.doctorTitle = patientAppointmentHistoryResponseModel.doctorTitle;
selectedDoctor.nationalityFlagURL = "";
selectedDoctor.speciality = patientAppointmentHistoryResponseModel.doctorSpeciality;
selectedDoctor.clinicName = patientAppointmentHistoryResponseModel.clinicName;
selectedDoctor.projectName = patientAppointmentHistoryResponseModel.projectName;
selectedDoctor.clinicID = patientAppointmentHistoryResponseModel.clinicID;
selectedDoctor.projectID = patientAppointmentHistoryResponseModel.projectID;
getIt.get<BookAppointmentsViewModel>().setSelectedDoctor(selectedDoctor);
LoaderBottomSheet.showLoader();
await getIt.get<BookAppointmentsViewModel>().getDoctorProfile(onSuccess: (dynamic respData) {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: DoctorProfilePage(
isDoctorAllowedToBook: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false),
),
),
);
}, onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).onPress(() async {
// DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel();
// selectedDoctor.doctorID = patientAppointmentHistoryResponseModel.doctorID;
// selectedDoctor.doctorImageURL = patientAppointmentHistoryResponseModel.doctorImageURL;
// selectedDoctor.name = patientAppointmentHistoryResponseModel.doctorNameObj;
// selectedDoctor.doctorTitle = patientAppointmentHistoryResponseModel.doctorTitle;
// selectedDoctor.nationalityFlagURL = "";
// selectedDoctor.speciality = patientAppointmentHistoryResponseModel.doctorSpeciality;
// selectedDoctor.clinicName = patientAppointmentHistoryResponseModel.clinicName;
// selectedDoctor.projectName = patientAppointmentHistoryResponseModel.projectName;
// selectedDoctor.clinicID = patientAppointmentHistoryResponseModel.clinicID;
// selectedDoctor.projectID = patientAppointmentHistoryResponseModel.projectID;
//
// getIt.get<BookAppointmentsViewModel>().setSelectedDoctor(selectedDoctor);
// LoaderBottomSheet.showLoader();
// await getIt.get<BookAppointmentsViewModel>().getDoctorProfile(onSuccess: (dynamic respData) {
// LoaderBottomSheet.hideLoader();
// Navigator.of(context).push(
// CustomPageRoute(
// page: DoctorProfilePage(
// isDoctorAllowedToBook: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false),
// ),
// ),
// );
// }, onError: (err) {
// LoaderBottomSheet.hideLoader();
// showCommonBottomSheetWithoutHeight(
// context,
// child: Utils.getErrorWidget(loadingText: err),
// callBackFunc: () {},
// isFullScreen: false,
// isCloseButtonVisible: true,
// );
// });
})
],
),
@ -223,7 +210,48 @@ class AppointmentDoctorCard extends StatelessWidget {
],
),
),
);
).onPress(() async {
if ((patientAppointmentHistoryResponseModel.isLiveCareAppointment! ||
patientAppointmentHistoryResponseModel.isExecludeDoctor! ||
patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! == false ||
patientAppointmentHistoryResponseModel.isActiveDoctor! == false ||
!Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID))) {
} else {
DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel();
selectedDoctor.doctorID = patientAppointmentHistoryResponseModel.doctorID;
selectedDoctor.doctorImageURL = patientAppointmentHistoryResponseModel.doctorImageURL;
selectedDoctor.name = patientAppointmentHistoryResponseModel.doctorNameObj;
selectedDoctor.doctorTitle = patientAppointmentHistoryResponseModel.doctorTitle;
selectedDoctor.nationalityFlagURL = "";
selectedDoctor.speciality = patientAppointmentHistoryResponseModel.doctorSpeciality;
selectedDoctor.clinicName = patientAppointmentHistoryResponseModel.clinicName;
selectedDoctor.projectName = patientAppointmentHistoryResponseModel.projectName;
selectedDoctor.clinicID = patientAppointmentHistoryResponseModel.clinicID;
selectedDoctor.projectID = patientAppointmentHistoryResponseModel.projectID;
getIt.get<BookAppointmentsViewModel>().setSelectedDoctor(selectedDoctor);
LoaderBottomSheet.showLoader();
await getIt.get<BookAppointmentsViewModel>().getDoctorProfile(onSuccess: (dynamic respData) {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: DoctorProfilePage(
isDoctorAllowedToBook: !(patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false),
),
),
);
}, onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
}
});
}
Widget getAppointmentActionButtons(bool isAppointmentArrived) {
@ -235,7 +263,7 @@ class AppointmentDoctorCard extends StatelessWidget {
onAskDoctorTap();
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
isBold: true,
@ -275,7 +303,7 @@ class AppointmentDoctorCard extends StatelessWidget {
onCancelTap();
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
borderColor: Colors.transparent,
textColor: AppColors.whiteColor,
fontSize: 14.f,
isBold: true,
@ -294,7 +322,7 @@ class AppointmentDoctorCard extends StatelessWidget {
onRescheduleTap();
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
isBold: true,

@ -142,9 +142,9 @@ class _AppointmentRatingWidgetState extends State<AppointmentRatingWidget> {
Expanded(
child: CustomButton(
text: LocaleKeys.later.tr(context: context),
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
onPressed: () {
Navigator.pop(context);
},

@ -26,7 +26,7 @@ class BuildDoctorRowAppointmentRating extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.buildImgWithNetwork(
url: patientAppointmentHistoryResponseModel.doctorImageURL!,
url: patientAppointmentHistoryResponseModel.doctorImageURL,
iconColor: AppColors.transparent,
width: 63.h,
height: 63.h,

@ -16,6 +16,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart' show RegionListItem;
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:lottie/lottie.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
@ -117,6 +118,14 @@ class _RegionBottomSheetBodyState extends State<RegionBottomSheetBody> {
height: MediaQuery.of(context).size.height * 0.5,
child: Column(
children: [
// AppCustomChipWidget(
// richText: Row(
// children: [
// "${appState.getAuthenticatedUser()!.age} ".toText10(color: AppColors.blackColor, isBold: true),
// LocaleKeys.yearsOld.tr(context: context).toText10(color: AppColors.blackColor, isBold: true),
// ],
// ),
// ),
// Consumer<AppointmentViaRegionViewmodel>(
// builder: (context, regionVM, _) {
// return Padding(

@ -88,7 +88,7 @@ class AskDoctorAppointmentCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Utils.buildImgWithNetwork(
url: askDoctorAppointmentHistoryList.doctorImageURL!,
url: askDoctorAppointmentHistoryList.doctorImageURL,
iconColor: AppColors.blackBgColor,
width: 63.h,
height: 63.h,

@ -94,17 +94,18 @@ class QuickLoginState extends State<QuickLogin> {
mainAxisAlignment: MainAxisAlignment.end,
children: [
Expanded(
child: CustomButton(
text: LocaleKeys.enableQuickLoginBtn.tr(context: context),
onPressed: () {
widget.onPressed();
},
backgroundColor: Color(0xffED1C2B),
borderColor: Color(0xffED1C2B),
textColor: Colors.white,
icon: AppAssets.apple_finder,
height: 56.h,
)),
child: CustomButton(
text: LocaleKeys.enableQuickLoginBtn.tr(context: context),
onPressed: () {
widget.onPressed();
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: Colors.white,
icon: AppAssets.apple_finder,
height: 56.h,
),
),
],
),
SizedBox(
@ -120,9 +121,9 @@ class QuickLoginState extends State<QuickLogin> {
cacheService.saveBool(key: CacheConst.quickLoginEnabled, value: false);
Navigator.pop(context, "true");
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Colors.red,
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
height: 56.h,
// icon: "assets/images/svg/apple-finder.svg",
)),

@ -17,6 +17,9 @@ import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart';
import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart' show CustomButton;
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/date_range_calender.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/models/calendar_design_type.dart';
import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:provider/provider.dart';
@ -153,30 +156,56 @@ class _RegisterNew extends State<RegisterNew> {
},
).withVerticalPadding(8),
Divider(height: 1),
TextInputWidget(
labelText: LocaleKeys.dob.tr(context: context),
hintText: "11 July, 1994",
controller: authVm.dobController,
focusNode: _dobFocusNode,
isEnable: true,
prefix: null,
isAllowRadius: true,
isBorderAllowed: false,
isAllowLeadingIcon: true,
padding: EdgeInsets.symmetric(vertical: 8.h),
leadingIcon: AppAssets.birthday_cake,
selectionType: SelectionTypeEnum.calendar,
onCalendarTypeChanged: authVm.onCalenderTypeChange,
onChange: (value) {
authVm.onDobChange(value);
// Clear error when user selects a date
authVm.clearDobError();
GestureDetector(
onTap: () async {
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.selectDate.tr(context: context),
context,
child: DateRangeSelector(
onRangeSelected: (DateTime? start, DateTime? end) {
if (start != null) {
var picked = start.toString().split(" ").first;
// authVm.dobController.text = picked;
authVm.onDobChange(picked);
// Clear error when user selects a date
authVm.clearDobError();
}
},
designType: CalendarDesignType.designV2,
showCalendarToggle: true,
onCalendarTypeChanged: null,
btnTitle: LocaleKeys.done.tr(context: context),
),
isFullScreen: false,
isCloseButtonVisible: true,
callBackFunc: () {},
);
},
fontFamily: "Poppins",
hasError: false,
// Don't show individual field border
errorMessage: authVm.dobError, // Show error message if exists
).withVerticalPadding(8),
child: TextInputWidget(
labelText: LocaleKeys.dob.tr(context: context),
hintText: "11 July, 1994",
controller: authVm.dobController,
focusNode: _dobFocusNode,
isEnable: false,
prefix: null,
isAllowRadius: true,
isBorderAllowed: false,
isAllowLeadingIcon: true,
padding: EdgeInsets.symmetric(vertical: 8.h),
leadingIcon: AppAssets.birthday_cake,
selectionType: SelectionTypeEnum.calendar,
onCalendarTypeChanged: authVm.onCalenderTypeChange,
onChange: (value) {
authVm.onDobChange(value);
// Clear error when user selects a date
authVm.clearDobError();
},
fontFamily: "Poppins",
hasError: false,
// Don't show individual field border
errorMessage: authVm.dobError, // Show error message if exists
).withVerticalPadding(8),
),
],
),
),

@ -16,6 +16,7 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_view_
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/navigation_screen.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart';
import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart';
@ -30,11 +31,24 @@ class SavedLogin extends StatefulWidget {
_SavedLogin createState() => _SavedLogin();
}
class _SavedLogin extends State<SavedLogin> {
class _SavedLogin extends State<SavedLogin> with RouteAware {
LoginTypeEnum loginType = LoginTypeEnum.sms;
late AuthenticationViewModel authVm;
late AppState appState;
bool? isOther;
PageRoute<dynamic>? _pageRoute;
void _assignSavedLoginData() {
authVm.phoneNumberController.text = appState.getSelectDeviceByImeiRespModelElement!.mobile!.startsWith("0")
? appState.getSelectDeviceByImeiRespModelElement!.mobile!.replaceFirst("0", "")
: appState.getSelectDeviceByImeiRespModelElement!.mobile!;
authVm.nationalIdController.text = appState.getSelectDeviceByImeiRespModelElement!.identificationNo!;
if (isOther == true) {
authVm.phoneNumberController.text = appState.getSelectDeviceByImeiRespModelElement!.mobile!;
authVm.selectedCountrySignup = CountryEnum.others;
}
}
@override
void initState() {
@ -43,19 +57,10 @@ class _SavedLogin extends State<SavedLogin> {
appState = getIt.get<AppState>();
loginType = LoginTypeExtension.fromValue(appState.getSelectDeviceByImeiRespModelElement!.logInType!)!;
authVm.phoneNumberController.text = appState.getSelectDeviceByImeiRespModelElement!.mobile!.startsWith("0")
? appState.getSelectDeviceByImeiRespModelElement!.mobile!.replaceFirst("0", "")
: appState.getSelectDeviceByImeiRespModelElement!.mobile!;
authVm.nationalIdController.text = appState.getSelectDeviceByImeiRespModelElement!.identificationNo!;
// Check if this is an "Others" country user
isOther = appState.getSelectDeviceByImeiRespModelElement!.isOther == true;
// Set country to Others if isOther flag is true - this ensures _Others APIs are called
if (isOther!) {
authVm.phoneNumberController.text = appState.getSelectDeviceByImeiRespModelElement!.mobile!;
authVm.selectedCountrySignup = CountryEnum.others;
}
_assignSavedLoginData();
if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) {
authVm.loginWithFingerPrintFace(() {});
@ -63,6 +68,30 @@ class _SavedLogin extends State<SavedLogin> {
super.initState();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
final route = ModalRoute.of(context);
if (route is PageRoute && route != _pageRoute) {
if (_pageRoute != null) {
getIt.get<NavigationService>().routeObserver.unsubscribe(this);
}
_pageRoute = route;
getIt.get<NavigationService>().routeObserver.subscribe(this, route);
}
}
@override
void didPopNext() {
_assignSavedLoginData();
}
@override
void dispose() {
getIt.get<NavigationService>().routeObserver.unsubscribe(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -298,9 +327,9 @@ class _SavedLogin extends State<SavedLogin> {
// MaterialPageRoute(builder: (BuildContext context) => LandingNavigation())
// );
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
fontSize: 16.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
@ -323,9 +352,9 @@ class _SavedLogin extends State<SavedLogin> {
MaterialPageRoute(builder: (BuildContext context) => LoginScreen()),
);
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
fontSize: 15.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,

@ -232,7 +232,7 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
Container(
padding: EdgeInsets.all(16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.bgRedLightColor.withValues(alpha: 0.1),
color: AppColors.secondaryLightRedColorBase.withValues(alpha: 0.1),
borderRadius: 12.r,
hasShadow: false,
),

@ -67,7 +67,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
immediateLiveCareViewModel.initImmediateLiveCare();
if (appState.isAuthenticated) {
getIt.get<MyAppointmentsViewModel>().getPatientMyDoctors(isTop8: true);
getIt.get<MyAppointmentsViewModel>().getPatientFavouriteDoctors();
// getIt.get<MyAppointmentsViewModel>().getPatientFavouriteDoctors();
}
});
WidgetsBinding.instance.addPostFrameCallback((_) {
@ -106,8 +106,8 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
children: [
SizedBox(height: 16.h),
CustomTabBar(
activeTextColor: Color(0xffED1C2B),
activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
activeTextColor: AppColors.primaryRedColor,
activeBackgroundColor: AppColors.secondaryLightRedColor,
initialIndex: bookAppointmentsVM.selectedTabIndex,
tabs: [
CustomTabBarModel(null, LocaleKeys.general.tr(context: context)),
@ -196,7 +196,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Utils.buildImgWithNetwork(
url: myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!,
url: myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL,
iconColor: AppColors.transparent,
width: 64.h,
height: 64.h,
@ -326,9 +326,9 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
onPressed: () {
Navigator.of(context).push(CustomPageRoute(page: SearchDoctorByName()));
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
isBold: true,
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),
@ -389,7 +389,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Utils.buildImgWithNetwork(
url: myAppointmentsVM.patientFavouriteDoctorsList[index].doctorImageUrl!,
url: myAppointmentsVM.patientFavouriteDoctorsList[index].doctorImageUrl,
iconColor: AppColors.transparent,
width: 64.h,
height: 64.h,
@ -523,6 +523,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
],
).onPress(() {
bookAppointmentsViewModel.setIsDoctorSearchByNameStarted(false);
bookAppointmentsViewModel.setIsLiveCareSchedule(false);
bookAppointmentsViewModel.setProjectID(null);
Navigator.of(context).push(
CustomPageRoute(
@ -552,7 +553,9 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
Clarity.sendCustomEvent('Select_Region_Page');
bookAppointmentsViewModel.setProjectID(null);
bookAppointmentsViewModel.setIsLiveCareSchedule(false);
openRegionListBottomSheet(context, RegionBottomSheetType.FOR_REGION);
}),
],
@ -610,6 +613,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
),
);
} else {
immediateLiveCareViewModel.initImmediateLiveCare();
Navigator.of(context).push(
CustomPageRoute(
page: SelectImmediateLiveCareClinicPage(),

@ -1,3 +1,4 @@
import 'package:clarity_flutter/clarity_flutter.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -8,6 +9,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/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart';
@ -29,6 +31,7 @@ class DoctorProfilePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
Clarity.setCurrentScreenName('Doctor Profile Page');
final bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
final appState = getIt.get<AppState>();
return Scaffold(
@ -57,9 +60,7 @@ class DoctorProfilePage extends StatelessWidget {
doctorID: viewModel.doctorsProfileResponseModel.doctorID ?? 0,
isActive: viewModel.isFavouriteDoctor,
onSuccess: (response) {
Utils.showToast(viewModel.isFavouriteDoctor
? LocaleKeys.doctorAddedToFavourite.tr(context: context)
: LocaleKeys.doctorRemovedFromFavourite.tr(context: context));
Utils.showToast(viewModel.isFavouriteDoctor ? LocaleKeys.doctorAddedToFavourite.tr(context: context) : LocaleKeys.doctorRemovedFromFavourite.tr(context: context));
// Successfully added/removed favorite - refresh the favorites list
getIt.get<MyAppointmentsViewModel>().refreshFavouriteDoctors();
},
@ -86,7 +87,7 @@ class DoctorProfilePage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.buildImgWithNetwork(
url: bookAppointmentsViewModel.doctorsProfileResponseModel.doctorImageURL!,
url: bookAppointmentsViewModel.doctorsProfileResponseModel.doctorImageURL,
iconColor: AppColors.transparent,
width: 63.h,
height: 63.h,
@ -98,14 +99,11 @@ class DoctorProfilePage extends StatelessWidget {
children: [
SizedBox(
width: isFoldable ? 250.w : 220.w,
child:
("${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorTitleForProfile} ${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName}")
.toString()
.toText24(isBold: true),
child: ("${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorTitleForProfile} ${bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName}")
.toString()
.toText24(isBold: true),
),
(bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.isNotEmpty
? bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.first
: "")
(bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.isNotEmpty ? bookAppointmentsViewModel.doctorsProfileResponseModel.specialty!.first : "")
.toString()
.toText18(isBold: true, color: AppColors.primaryRedColor),
],
@ -113,7 +111,7 @@ class DoctorProfilePage extends StatelessWidget {
],
),
Utils.buildImgWithNetwork(
url: bookAppointmentsViewModel.doctorsProfileResponseModel.nationalityFlagURL!,
url: bookAppointmentsViewModel.doctorsProfileResponseModel.nationalityFlagURL,
iconColor: AppColors.transparent,
width: 32.h,
height: 32.h,
@ -145,16 +143,12 @@ class DoctorProfilePage extends StatelessWidget {
children: [
Column(
children: [
Utils.buildSvgWithAssets(
icon: AppAssets.doctor_profile_rating_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false),
Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_rating_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false),
SizedBox(height: 16.h),
LocaleKeys.ratings.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor),
bookAppointmentsViewModel.doctorsProfileResponseModel.decimalDoctorRate.toString().toText16(
isBold: true,
color: AppColors.textColor,
isUnderLine: true,
decorationColor: AppColors.textColor,
fontFamily: "Poppins"),
bookAppointmentsViewModel.doctorsProfileResponseModel.decimalDoctorRate
.toString()
.toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor, fontFamily: "Poppins"),
],
).onPress(() {
bookAppointmentsViewModel.getDoctorRatingDetails();
@ -170,18 +164,12 @@ class DoctorProfilePage extends StatelessWidget {
SizedBox(width: 36.w),
Column(
children: [
Utils.buildSvgWithAssets(
icon: AppAssets.doctor_profile_reviews_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false),
Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_reviews_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false),
SizedBox(height: 16.h),
LocaleKeys.reviews.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor),
NumberFormat.decimalPattern("en_US")
.format(bookAppointmentsViewModel.doctorsProfileResponseModel.noOfPatientsRate ?? 0)
.toText16(
isBold: true,
color: AppColors.textColor,
isUnderLine: true,
decorationColor: AppColors.textColor,
fontFamily: "Poppins"),
.toText16(isBold: true, color: AppColors.textColor, isUnderLine: true, decorationColor: AppColors.textColor, fontFamily: "Poppins"),
],
).onPress(() {
bookAppointmentsViewModel.getDoctorRatingDetails();
@ -212,9 +200,7 @@ class DoctorProfilePage extends StatelessWidget {
SizedBox(height: 16.h),
LocaleKeys.information.tr(context: context).toText14(isBold: true, color: AppColors.textColor),
SizedBox(height: 6.h),
(bookAppointmentsViewModel.doctorsProfileResponseModel.doctorProfileInfo ?? "")
.trim()
.toText12(isBold: true, color: AppColors.greyTextColor),
(bookAppointmentsViewModel.doctorsProfileResponseModel.doctorProfileInfo ?? "").trim().toText12(isBold: true, color: AppColors.greyTextColor),
SizedBox(height: 24.h),
],
).paddingSymmetrical(24.h, 0.h),
@ -235,11 +221,18 @@ class DoctorProfilePage extends StatelessWidget {
bookAppointmentsViewModel.selectedDoctor.specialityN = bookAppointmentsViewModel.doctorsProfileResponseModel.specialty;
bookAppointmentsViewModel.selectedDoctor.name = bookAppointmentsViewModel.doctorsProfileResponseModel.doctorName;
bookAppointmentsViewModel.selectedDoctor.doctorImageURL = bookAppointmentsViewModel.doctorsProfileResponseModel.doctorImageURL;
bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL =
bookAppointmentsViewModel.doctorsProfileResponseModel.nationalityFlagURL;
bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL = bookAppointmentsViewModel.doctorsProfileResponseModel.nationalityFlagURL;
bookAppointmentsViewModel.selectedDoctor.clinicName = bookAppointmentsViewModel.doctorsProfileResponseModel.clinicDescription;
bookAppointmentsViewModel.selectedDoctor.projectName = bookAppointmentsViewModel.doctorsProfileResponseModel.projectName;
final clinic = GetClinicsListResponseModel(
clinicID: bookAppointmentsViewModel.doctorsProfileResponseModel.clinicID,
clinicDescription: bookAppointmentsViewModel.doctorsProfileResponseModel.clinicDescription,
clinicDescriptionN: bookAppointmentsViewModel.doctorsProfileResponseModel.clinicDescription,
);
bookAppointmentsViewModel.setSelectedClinic(clinic);
LoaderBottomSheet.showLoader();
bookAppointmentsViewModel.isLiveCareSchedule
? await bookAppointmentsViewModel.getLiveCareDoctorFreeSlots(

@ -80,8 +80,8 @@ class LaserAppointment extends StatelessWidget {
child: Column(
children: [
CustomTabBar(
activeTextColor: Color(0xffED1C2B),
activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
activeTextColor: AppColors.primaryRedColor,
activeBackgroundColor: AppColors.secondaryLightRedColor,
tabs: [
CustomTabBarModel(null, LocaleKeys.malE.tr()),
CustomTabBarModel(null, LocaleKeys.femaleGender.tr()),

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/api_consts.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';
@ -15,6 +16,7 @@ import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_liveca
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_payment_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/book_appointment/livecare/livecare_terms_conditions.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart';
@ -286,7 +288,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "ريال").toLowerCase() == "ريال")),
],
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 17.h),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -299,18 +301,59 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "ريال").toLowerCase() == "ريال")),
],
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 16.h),
GestureDetector(
onTap: immediateLiveCareVM.onTermsAccepted,
child: Row(
children: [
Selector<ImmediateLiveCareViewModel, bool>(
selector: (_, viewModel) => viewModel.isTermsAccepted,
shouldRebuild: (previous, next) => previous != next,
builder: (context, isTermsAccepted, child) {
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
height: 24.h,
width: 24.h,
decoration: BoxDecoration(
color: isTermsAccepted ? AppColors.primaryRedColor : Colors.transparent,
borderRadius: BorderRadius.circular(6),
border: Border.all(color: isTermsAccepted ? AppColors.primaryRedBorderColor : AppColors.greyColor, width: 2.h),
),
child: isTermsAccepted ? Icon(Icons.check, size: 16.f, color: Colors.white) : null,
);
},
),
SizedBox(width: 12.h),
Row(
children: [
"${LocaleKeys.iAcceptThe.tr(context: context)} ".toText14(isBold: true),
GestureDetector(
onTap: () {
Navigator.push(
context,
CustomPageRoute(
page: LiveCareTermsConditions(),
),
);
},
child: LocaleKeys.termsConditoins.tr(context: context).toText14(isBold: true, isUnderLine: true, color: AppColors.primaryRedColor)),
],
),
],
),
).paddingSymmetrical(24.h, 0.h),
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0" ||
immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0.0")
// (true)
? CustomButton(
isDisabled: !immediateLiveCareVM.isTermsAccepted,
text: LocaleKeys.confirmLiveCare.tr(context: context),
onPressed: () async {
await askVideoCallPermission(context).then((val) async {
if (val) {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingLiveCareRequest.tr(context: context));
await immediateLiveCareVM.addNewCallRequestForImmediateLiveCare(
"${appState.getAuthenticatedUser()!.patientId}${DateTime.now().millisecondsSinceEpoch}");
await immediateLiveCareVM.addNewCallRequestForImmediateLiveCare("${appState.getAuthenticatedUser()!.patientId}${DateTime.now().millisecondsSinceEpoch}");
await immediateLiveCareVM.getPatientLiveCareHistory();
LoaderBottomSheet.hideLoader();
if (immediateLiveCareVM.patientHasPendingLiveCareRequest) {
@ -367,6 +410,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
iconSize: 18.h,
).paddingSymmetrical(24.h, 24.h)
: CustomButton(
isDisabled: !immediateLiveCareVM.isTermsAccepted,
text: LocaleKeys.payNow.tr(context: context),
onPressed: () async {
await askVideoCallPermission(context).then((val) {

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:clarity_flutter/clarity_flutter.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
@ -10,8 +11,10 @@ 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/myFatoorah_config.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart';
import 'package:hmg_patient_app_new/features/myfatoorah/myfatoorah_view_model.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';
@ -32,6 +35,8 @@ 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:myfatoorah_flutter/MFEnums.dart';
import 'package:myfatoorah_flutter/myfatoorah_flutter.dart';
import 'package:provider/provider.dart';
import 'package:smooth_corner/smooth_corner.dart';
@ -48,6 +53,9 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
late MyAppointmentsViewModel myAppointmentsViewModel;
late AppState appState;
late PayTabsViewModel paytabsViewModel;
late MyFatoorahViewModel myFatoorahViewModel;
late MFApplePayButton mfApplePayButton;
MyInAppBrowser? browser;
String selectedPaymentMethod = "";
@ -60,8 +68,14 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
@override
void initState() {
Clarity.setCurrentScreenName('LiveCare Payment Page');
scheduleMicrotask(() {
payfortViewModel.initPayfortViewModel();
if (appState.isMyFatoorahEnabled) {
myFatoorahViewModel.initApplePayPayment(num.parse((immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0")));
}
myAppointmentsViewModel.getTamaraInstallmentsDetails().then((val) {
if (myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel != null) {
if (num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total!) >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! &&
@ -78,11 +92,15 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
@override
Widget build(BuildContext context) {
mfApplePayButton = MFApplePayButton(
applePayStyle: MFApplePayStyle(),
);
appState = getIt.get<AppState>();
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context, listen: false);
immediateLiveCareViewModel = Provider.of<ImmediateLiveCareViewModel>(context, listen: false);
payfortViewModel = Provider.of<PayfortViewModel>(context, listen: false);
paytabsViewModel = Provider.of<PayTabsViewModel>(context, listen: false);
myFatoorahViewModel = Provider.of<MyFatoorahViewModel>(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
@ -129,12 +147,83 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "MADA";
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration(
"LiveCare Payment",
num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0"),
if (appState.isMyFatoorahEnabled) {
myFatoorahViewModel.initiateCardPayment(
amount: num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0"),
paymentDescription: "LiveCare Payment",
displayCurrencyIso: appState.getAuthenticatedUser()!.outSa == 0
? ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat
? MFCurrencyISO.KUWAIT_KWD
: MFCurrencyISO.SAUDIARABIA_SAR
: MFCurrencyISO.UAE_AED,
paymentOption: selectedPaymentMethod,
liveServiceID: immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID.toString(),
isScheduleLiveCare: "false",
appointmentDate: DateUtil.convertDateToString(DateTime.now()),
appointmentNo: "0",
doctorID: "0",
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
serviceID: ServiceTypeEnum.liveCareAppointment.getIdFromServiceEnum().toString(),
projectID: (ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12).toString(),
onSuccess: (response) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr());
await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare(response.invoiceId.toString());
await immediateLiveCareViewModel.getPatientLiveCareHistory();
LoaderBottomSheet.hideLoader();
if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
Navigator.of(context).push(
CustomPageRoute(
page: ImmediateLiveCarePendingRequestPage(),
),
);
} else {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "Unknown error occurred..."),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
},
onError: (errMessage) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: errMessage.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else if (appState.isPaytabsEnabled) {
transID = Utils.getAppointmentTransID(
immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID!,
ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12,
DateTime.now().millisecondsSinceEpoch,
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
await paytabsViewModel.setPaymentConfiguration("LiveCare Payment", num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0"),
ServiceTypeEnum.liveCareAppointment.getIdFromServiceEnum(), (ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12));
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID.toString(),
isScheduleLiveCare: "false",
appointmentDate: DateUtil.convertDateToString(DateTime.now()),
appointmentNo: "0",
doctorID: "0",
transactionID: transID,
amount: immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total.toString(),
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
orderDescription: "LiveCare Payment",
serviceID: ServiceTypeEnum.liveCareAppointment.getIdFromServiceEnum().toString(),
projectID: (ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12).toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr());
await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare(transactionData.transactionReference!);
await immediateLiveCareViewModel.getPatientLiveCareHistory();
@ -280,12 +369,83 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "VISA";
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration(
"LiveCare Payment",
num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0"),
if (appState.isMyFatoorahEnabled) {
myFatoorahViewModel.initiateCardPayment(
amount: num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0"),
paymentDescription: "LiveCare Payment",
displayCurrencyIso: appState.getAuthenticatedUser()!.outSa == 0
? ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat
? MFCurrencyISO.KUWAIT_KWD
: MFCurrencyISO.SAUDIARABIA_SAR
: MFCurrencyISO.UAE_AED,
paymentOption: selectedPaymentMethod,
liveServiceID: immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID.toString(),
isScheduleLiveCare: "false",
appointmentDate: DateUtil.convertDateToString(DateTime.now()),
appointmentNo: "0",
doctorID: "0",
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
serviceID: ServiceTypeEnum.liveCareAppointment.getIdFromServiceEnum().toString(),
projectID: (ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12).toString(),
onSuccess: (response) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr());
await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare(response.invoiceId.toString());
await immediateLiveCareViewModel.getPatientLiveCareHistory();
LoaderBottomSheet.hideLoader();
if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
Navigator.of(context).push(
CustomPageRoute(
page: ImmediateLiveCarePendingRequestPage(),
),
);
} else {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "Unknown error occurred..."),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
},
onError: (errMessage) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: errMessage.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else if (appState.isPaytabsEnabled) {
transID = Utils.getAppointmentTransID(
immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID!,
ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12,
DateTime.now().millisecondsSinceEpoch,
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
await paytabsViewModel.setPaymentConfiguration("LiveCare Payment", num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0"),
ServiceTypeEnum.liveCareAppointment.getIdFromServiceEnum(), (ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12));
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID.toString(),
isScheduleLiveCare: "false",
appointmentDate: DateUtil.convertDateToString(DateTime.now()),
appointmentNo: "0",
doctorID: "0",
transactionID: transID,
amount: immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total.toString(),
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
orderDescription: "LiveCare Payment",
serviceID: ServiceTypeEnum.liveCareAppointment.getIdFromServiceEnum().toString(),
projectID: (ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12).toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr());
await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare(transactionData.transactionReference!);
await immediateLiveCareViewModel.getPatientLiveCareHistory();
@ -426,7 +586,62 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "TAMARA";
openPaymentURL("tamara");
transID = Utils.getAppointmentTransID(
immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID!,
ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12,
DateTime.now().millisecondsSinceEpoch,
);
if (appState.isPaytabsEnabled) {
paytabsViewModel.setAlternatePaymentConfiguration(
"LiveCare Payment", num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total!),
ServiceTypeEnum.liveCareAppointment.getIdFromServiceEnum(),
(ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12));
paytabsViewModel.startAlternatePayment(
liveServiceID: immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID.toString(),
appointmentDate: "",
appointmentNo: "",
doctorID: "",
transactionID: transID,
amount: immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0",
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
serviceID: "4",
projectID: "12",
isSchedule: false,
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr());
tamaraOrderID = transactionData.cartID!;
await payfortViewModel.updateTamaraRequestStatus(responseMessage: "success", status: "14", clientRequestID: transID, tamaraOrderID: tamaraOrderID);
await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare(transID);
await immediateLiveCareViewModel.getPatientLiveCareHistory();
LoaderBottomSheet.hideLoader();
if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
Navigator.of(context).push(
CustomPageRoute(
page: ImmediateLiveCarePendingRequestPage(),
),
);
} else {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "Unknown error occurred..."),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
},
onError: (err) async {},
);
} else {
openPaymentURL("TAMARA");
}
})
: SizedBox.shrink(),
],
@ -488,17 +703,24 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.amountBeforeTax.tr().toText14(isBold: true),
Utils.getPaymentAmountWithSymbol((immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.amount ?? "0.0").toString().toText16(isBold: true), AppColors.blackColor, 13,
isSaudiCurrency: true),
ClarityUnmask(
child: Utils.getPaymentAmountWithSymbol(
(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.amount ?? "0.0").toString().toText16(isBold: true), AppColors.blackColor, 13,
isSaudiCurrency: true),
),
],
).paddingSymmetrical(24.h, 0.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.vat15.tr().toText14(isBold: true, color: AppColors.greyTextColor),
Utils.getPaymentAmountWithSymbol(
(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.tax ?? "0.0").toString().toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13,
isSaudiCurrency: true),
ClarityUnmask(
child: Utils.getPaymentAmountWithSymbol(
(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.tax ?? "0.0").toString().toText14(isBold: true, color: AppColors.greyTextColor),
AppColors.greyTextColor,
13,
isSaudiCurrency: true),
),
],
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 17.h),
@ -506,21 +728,39 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"".toText14(isBold: true),
Utils.getPaymentAmountWithSymbol((immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0").toString().toText24(isBold: true), AppColors.blackColor, 17,
isSaudiCurrency: true),
ClarityUnmask(
child: Utils.getPaymentAmountWithSymbol(
(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0").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,
applyThemeColor: false
).paddingSymmetrical(24.h, 0.h).onPress(() {
?
// appState.isMyFatoorahEnabled
// ? SizedBox(
// height: 50.h,
// width: double.infinity,
// child: Stack(
// children: [
// Positioned.fill(child: mfApplePayButton),
// Positioned.fill(
// child: GestureDetector(
// behavior: HitTestBehavior.opaque,
// onTap: () {},
// ),
// ),
// ],
// ),
// ).paddingSymmetrical(24.h, 16.h)
// :
Utils.buildSvgWithAssets(icon: AppAssets.apple_pay_button, width: 200.h, height: 80.h, fit: BoxFit.contain, applyThemeColor: false)
.paddingSymmetrical(24.h, 0.h)
.onPress(() async {
// payfortVM.setIsApplePayConfigurationLoading(true);
if (Utils.havePrivilege(103)) {
startApplePay();
// startApplePay();
myFatoorahViewModel.executeApplePayPayment();
} else {
openPaymentURL("ApplePay");
}
@ -716,9 +956,28 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
);
ApplePayInsertRequest applePayInsertRequest = ApplePayInsertRequest();
await payfortViewModel.getPayfortConfigurations(
serviceId: ServiceTypeEnum.liveCareAppointment.getIdFromServiceEnum(), projectId: ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12, integrationId: 2);
if (!appState.isPaytabsEnabled) {
await payfortViewModel.getPayfortConfigurations(
serviceId: ServiceTypeEnum.liveCareAppointment.getIdFromServiceEnum(),
projectId: ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat
? appState.getAuthenticatedUser()!.outSa == 0
? 15
: 2
: appState.getAuthenticatedUser()!.outSa == 0
? 12
: 2,
integrationId: 2);
} else {
await paytabsViewModel.setPaymentConfiguration("LiveCare Payment", num.parse((immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0")),
ServiceTypeEnum.liveCareAppointment.getIdFromServiceEnum(),
(ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat
? appState.getAuthenticatedUser()!.outSa == 0
? 15
: 2
: appState.getAuthenticatedUser()!.outSa == 0
? 12
: 2));
}
applePayInsertRequest.clientRequestID = transID;
applePayInsertRequest.clinicID = immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID!;
@ -732,7 +991,7 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
applePayInsertRequest.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken);
applePayInsertRequest.voipToken = await Utils.getStringFromPrefs(CacheConst.voipToken);
applePayInsertRequest.doctorID = 0;
applePayInsertRequest.projectID = "12";
applePayInsertRequest.projectID = appState.getAuthenticatedUser()!.outSa == 0 ? "12" : "2";
applePayInsertRequest.serviceID = ServiceTypeEnum.liveCareAppointment.getIdFromServiceEnum().toString();
applePayInsertRequest.channelID = 3;
applePayInsertRequest.patientID = appState.getAuthenticatedUser()!.patientId.toString();
@ -755,20 +1014,19 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
applePayInsertRequest.isMobSDK = true;
applePayInsertRequest.merchantReference = transID;
applePayInsertRequest.merchantIdentifier = payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier;
applePayInsertRequest.merchantIdentifier = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.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.signature = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.signature : payfortViewModel.payfortProjectDetailsRespModel!.signature;
applePayInsertRequest.accessCode = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.accessCode : payfortViewModel.payfortProjectDetailsRespModel!.accessCode;
applePayInsertRequest.shaRequestPhrase = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.shaRequest : payfortViewModel.payfortProjectDetailsRespModel!.shaRequest;
applePayInsertRequest.shaResponsePhrase = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.shaResponse : 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) {
await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest).then((value) async {
if (appState.isPaytabsEnabled) {
selectedPaymentMethod = "VISA";
LoaderBottomSheet.hideLoader();
paytabsViewModel.setPaymentConfiguration("Advance Payment", num.parse((immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0")));
paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr());
await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare(transactionData.transactionReference!);

@ -0,0 +1,230 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import '../../../generated/locale_keys.g.dart';
class LiveCareTermsConditions extends StatelessWidget {
const LiveCareTermsConditions({super.key});
@override
Widget build(BuildContext context) {
final isArabic = getIt.get<AppState>().isArabic();
return Scaffold(
backgroundColor: AppColors.scaffoldBgColor,
body: Column(
children: [
Expanded(
child: CollapsingListView(
title: LocaleKeys.livecare.tr(context: context),
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeading(context, LocaleKeys.liveCareTermsHeading.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildHeading(context, LocaleKeys.liveCareTermsIntroHeading.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions1.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions2.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildHeading(context, LocaleKeys.liveCareTermsHeading2.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions3.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions3a.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions3b.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions3c.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions3d.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions4.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions5.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions6.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildHeading(context, LocaleKeys.liveCareTermsHeading3.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions7.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions8.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions9.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions10.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions11.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions12.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions13.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildHeading(context, LocaleKeys.liveCareTermsHeading4.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions14.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions15.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions16.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildHeading(context, LocaleKeys.liveCareTermsHeading5.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions17.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions18.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions19.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions19a.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions19b.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions19c.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions19d.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions19e.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions20.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildHeading(context, LocaleKeys.liveCareTermsHeading6.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions21.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions22.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions23.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildHeading(context, LocaleKeys.liveCareTermsHeading7.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions24.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions25.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions26.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions27.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions28.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions29.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions30.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildHeading(context, LocaleKeys.liveCareTermsHeading8.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions31.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildHeading(context, LocaleKeys.liveCareTermsHeading9.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions32.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions33.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions34.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions35.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildHeading(context, LocaleKeys.liveCareTermsHeading10.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions36.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildHeading(context, LocaleKeys.liveCareTermsHeading11.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions37.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions38.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildHeading(context, LocaleKeys.liveCareTermsHeading12.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions39.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions40.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions41.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildHeading(context, LocaleKeys.liveCareTermsHeading13.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions42.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildHeading(context, LocaleKeys.liveCareTermsHeading14.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions43.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions43a.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions43b.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions43c.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions43d.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions43e.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions43f.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions43g.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildHeading(context, LocaleKeys.liveCareTermsHeading15.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions44.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions45.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions46.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildHeading(context, LocaleKeys.liveCareTermsHeading16.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions47.tr(context: context), isArabic),
SizedBox(height: 12.h),
_buildContent(context, LocaleKeys.liveCareTermsConditions48.tr(context: context), isArabic),
SizedBox(height: 24.h),
],
),
),
),
)
],
),
);
}
/// Helper method to build heading text with consistent styling
Widget _buildHeading(BuildContext context, String text, bool isArabic) {
return Text(
text,
style: TextStyle(
fontSize: 20.f,
fontFamily: isArabic ? 'CairoArabic' : 'Poppins',
fontWeight: FontWeight.w700,
color: AppColors.textColor,
letterSpacing: -0.5,
height: 1.4,
),
);
}
/// Helper method to build content text with consistent styling
Widget _buildContent(BuildContext context, String text, bool isArabic) {
return Text(
text,
style: TextStyle(
fontSize: 16.f,
fontFamily: isArabic ? 'CairoArabic' : 'Poppins',
color: AppColors.textColor,
letterSpacing: -0.3,
height: 1.5,
),
);
}
}

@ -424,6 +424,7 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
void initiateBookAppointment() async {
// LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context));
Clarity.sendCustomEvent('Appointment_book_success');
LoaderBottomSheet.showLoader(
loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment
? LocaleKeys.reschedulingAppo.tr(context: context)
@ -449,6 +450,7 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
callBackFunc: () {
bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false);
bookAppointmentsViewModel.setIsLiveCareSchedule(false);
bookAppointmentsViewModel.setSelectedChiefComplaintID(0);
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
@ -524,6 +526,7 @@ class _ReviewAppointmentPageState extends State<ReviewAppointmentPage> {
child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h), callBackFunc: () {
bookAppointmentsViewModel.setIsLiveCareSchedule(false);
bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false);
bookAppointmentsViewModel.setSelectedChiefComplaintID(0);
Navigator.pushAndRemoveUntil(context, CustomPageRoute(page: LandingNavigation()), (r) => false);
}, isFullScreen: false, isCloseButtonVisible: false, isAutoDismiss: true);
});

@ -3,15 +3,19 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart';
import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_filter/doctors_filter.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/doctor_profile_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/select_doctor_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/doctor_card.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@ -214,28 +218,46 @@ class _SearchDoctorByNameState extends State<SearchDoctorByName> {
bookAppointmentsViewModel: bookAppointmentsViewModel,
isDoctorNameSearch: true,
).paddingSymmetrical(16.h, 0.h).onPress(() async {
bookAppointmentsVM.setSelectedDoctor(bookAppointmentsVM.filteredDoctorList[index]);
LoaderBottomSheet.showLoader();
await bookAppointmentsVM.getDoctorProfile(
onSuccess: (dynamic respData) {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: DoctorProfilePage(isDoctorAllowedToBook: true),
),
);
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
);
if (getIt.get<AppState>().isAuthenticated && bookAppointmentsVM.filteredDoctorList[index].clinicID == 17 && getIt.get<AppState>().getAuthenticatedUser()!.age! < 12) {
bookAppointmentsViewModel.setProjectID(bookAppointmentsVM.filteredDoctorList[index].projectID.toString());
bookAppointmentsViewModel.setSelectedClinic(GetClinicsListResponseModel(
clinicID: bookAppointmentsVM.filteredDoctorList[index].clinicID,
clinicDescription: bookAppointmentsVM.filteredDoctorList[index].clinicName,
isLiveCareClinicAndOnline: false,
liveCareServiceID: 0,
liveCareClinicID: 0));
bookAppointmentsViewModel.setIsDoctorsListLoading(true);
Navigator.push(
context,
CustomPageRoute(
page: SelectDoctorPage(),
),
);
} else {
bookAppointmentsVM.setSelectedDoctor(bookAppointmentsVM.filteredDoctorList[index]);
LoaderBottomSheet.showLoader();
await bookAppointmentsVM.getDoctorProfile(
onSuccess: (dynamic respData) {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: DoctorProfilePage(isDoctorAllowedToBook: true),
),
);
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
);
}
// Column(
// children: bookAppointmentsVM.doctorsList[index].map<Widget>((entry) {
// final doctorIndex = entry.key;

File diff suppressed because it is too large Load Diff

@ -48,16 +48,20 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
Clarity.setCurrentScreenName('Select Doctor Page');
_scrollController = ScrollController();
scheduleMicrotask(() {
bookAppointmentsViewModel.setIsNearestAppointmentSelected(true);
bookAppointmentsViewModel.setIsNearestAppointmentSelected(false);
if (bookAppointmentsViewModel.isLiveCareSchedule) {
bookAppointmentsViewModel.getLiveCareDoctorsList();
} else {
if (bookAppointmentsViewModel.selectedClinic.clinicID == 17) {
bookAppointmentsViewModel.getDentalChiefComplaintDoctorsList();
if (appState.isAuthenticated && appState.getAuthenticatedUser()!.age! > 12) {
bookAppointmentsViewModel.getDentalChiefComplaintDoctorsList();
} else {
bookAppointmentsViewModel.getDoctorsList(isNearest: false);
}
} else if (bookAppointmentsViewModel.isGetDocForHealthCal) {
bookAppointmentsViewModel.getDoctorsListByHealthCal();
} else {
bookAppointmentsViewModel.getDoctorsList();
bookAppointmentsViewModel.getDoctorsList(isNearest: false);
}
}
});
@ -133,8 +137,28 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
isLoading: bookAppointmentsVM.isClinicsListLoading,
doctorsListResponseModel: bookAppointmentsVM.liveCareDoctorsList[index],
bookAppointmentsViewModel: bookAppointmentsViewModel,
).onPress(() {
// onLiveCareClinicSelected(bookAppointmentsVM.liveCareClinicsList[index]);
).onPress(() async {
bookAppointmentsVM.setSelectedDoctor(bookAppointmentsVM.liveCareDoctorsList[index]);
LoaderBottomSheet.showLoader();
await bookAppointmentsVM.getDoctorProfile(onSuccess: (dynamic respData) {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: DoctorProfilePage(
isDoctorAllowedToBook: true,
),
),
);
}, onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
}),
),
),

@ -138,12 +138,30 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "MADA";
if (appState.isPaytabsEnabled) {
transID = Utils.getAppointmentTransID(
bookAppointmentsViewModel.selectedDoctor.projectID!,
bookAppointmentsViewModel.selectedDoctor.clinicID!,
bookAppointmentsViewModel.selectedDoctor.doctorID!,
);
paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",
bookAppointmentsViewModel.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!,
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.bookingWaitingAppointment.tr(context: context));
ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), bookAppointmentsViewModel.selectedDoctor.projectID!);
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: "0",
isScheduleLiveCare: "false",
appointmentDate: DateUtil.convertDateToString(DateTime.now()),
appointmentNo: DateTime.now().millisecondsSinceEpoch.toString(),
doctorID: bookAppointmentsViewModel.selectedDoctor.doctorID.toString(),
transactionID: transID,
amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(),
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
orderDescription: "Appointment Payment",
serviceID: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum().toString(),
projectID: bookAppointmentsViewModel.selectedDoctor.projectID.toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.bookingWaitingAppointment.tr(context: context));
await bookAppointmentsViewModel.insertSpecificAppointmentForWalkIn(onSuccess: (val) async {
String appointmentNo = val.data['AppointmentNo'].toString();
await myAppointmentsViewModel.createAdvancePayment(
@ -289,12 +307,31 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "VISA";
if (appState.isPaytabsEnabled) {
transID = Utils.getAppointmentTransID(
bookAppointmentsViewModel.selectedDoctor.projectID!,
bookAppointmentsViewModel.selectedDoctor.clinicID!,
bookAppointmentsViewModel.selectedDoctor.doctorID!,
);
paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",
bookAppointmentsViewModel.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!,
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.bookingWaitingAppointment.tr(context: context));
ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), bookAppointmentsViewModel.selectedDoctor.projectID!);
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: "0",
isScheduleLiveCare: "false",
appointmentDate: DateUtil.convertDateToString(DateTime.now()),
appointmentNo: DateTime.now().millisecondsSinceEpoch.toString(),
doctorID: bookAppointmentsViewModel.selectedDoctor.doctorID.toString(),
transactionID: transID,
amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(),
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
orderDescription: "Appointment Payment",
serviceID: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum().toString(),
projectID: bookAppointmentsViewModel.selectedDoctor.projectID.toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.bookingWaitingAppointment.tr(context: context));
await bookAppointmentsViewModel.insertSpecificAppointmentForWalkIn(onSuccess: (val) async {
String appointmentNo = val.data['AppointmentNo'].toString();
await myAppointmentsViewModel.createAdvancePayment(
@ -434,7 +471,7 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "TAMARA";
openPaymentURL("tamara");
openPaymentURL("TAMARA");
})
: SizedBox.shrink(),
],
@ -817,7 +854,7 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",
bookAppointmentsViewModel.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!,
);
ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), bookAppointmentsViewModel.selectedDoctor.projectID!);
paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.bookingWaitingAppointment.tr(context: context));
await bookAppointmentsViewModel.insertSpecificAppointmentForWalkIn(onSuccess: (val) async {

@ -14,8 +14,10 @@ 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/book_appointments/models/resp_models/get_clinic_list_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/timeslots.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/dental_chief_complaints_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/review_appointment_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/waiting_appointment/waiting_appointment_info.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
@ -95,63 +97,116 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
// ),
SizedBox(
height: MediaQuery.of(context).size.height * 0.45,
child: Directionality(
textDirection: isArabic ? ui.TextDirection.rtl : ui.TextDirection.ltr,
child: Localizations.override(
context: context,
locale: isArabic ? const Locale('ar', 'SA') : const Locale('en'),
child: ClarityUnmask(
child: SfCalendar(
controller: _calendarController,
minDate: DateTime.now(),
showNavigationArrow: true,
// headerHeight: 60.h,
headerStyle: CalendarHeaderStyle(
backgroundColor: AppColors.transparent,
textAlign: isArabic ? TextAlign.end : TextAlign.start,
textStyle: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, letterSpacing: -0.46, color: AppColors.primaryRedColor),
),
viewHeaderStyle: ViewHeaderStyle(
// backgroundColor: AppColors.scaffoldBgColor,
dayTextStyle: TextStyle(fontSize: 14.f, fontWeight: FontWeight.w600, letterSpacing: -0.46, color: AppColors.textColor),
),
view: CalendarView.month,
todayHighlightColor: Colors.transparent,
todayTextStyle: TextStyle(color: AppColors.textColor, fontWeight: FontWeight.bold),
selectionDecoration: ShapeDecoration(
color: AppColors.transparent,
shape: SmoothRectangleBorder(
borderRadius: BorderRadius.circular(10.r),
smoothness: 1,
side: BorderSide(color: AppColors.primaryRedColor, width: 1.5),
child: Stack(
children: [
Directionality(
textDirection: isArabic ? ui.TextDirection.rtl : ui.TextDirection.ltr,
child: Localizations.override(
context: context,
locale: isArabic ? const Locale('ar', 'SA') : const Locale('en'),
child: ClarityUnmask(
child: SfCalendar(
controller: _calendarController,
minDate: DateTime.now(),
initialDisplayDate: DateUtil.convertStringToDate(bookAppointmentsViewModel.freeSlotsResponse[0]),
showNavigationArrow: false,
headerHeight: 60.h,
headerStyle: CalendarHeaderStyle(
backgroundColor: AppColors.transparent,
// textAlign: isArabic ? TextAlign.end : TextAlign.start,
textStyle: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, letterSpacing: -0.46, color: AppColors.primaryRedColor),
),
viewHeaderStyle: ViewHeaderStyle(
// backgroundColor: AppColors.scaffoldBgColor,
dayTextStyle: TextStyle(fontSize: 14.f, fontWeight: FontWeight.w600, letterSpacing: -0.46, color: AppColors.textColor),
),
view: CalendarView.month,
todayHighlightColor: Colors.transparent,
todayTextStyle: TextStyle(color: AppColors.textColor, fontWeight: FontWeight.bold),
selectionDecoration: ShapeDecoration(
color: AppColors.transparent,
shape: SmoothRectangleBorder(
borderRadius: BorderRadius.circular(10.r),
smoothness: 1,
side: BorderSide(color: AppColors.primaryRedColor, width: 1.5),
),
),
cellBorderColor: AppColors.transparent,
dataSource: MeetingDataSource(_getDataSource()),
monthCellBuilder: (context, details) => Padding(
padding: EdgeInsets.all(12.h),
child: details.date.day.toString().toText14(
isCenter: true,
color: details.date == _calendarController.selectedDate ? AppColors.primaryRedColor : AppColors.textColor,
isEnglishOnly: true,
),
),
monthViewSettings: MonthViewSettings(
dayFormat: "EEE",
appointmentDisplayMode: MonthAppointmentDisplayMode.indicator,
showTrailingAndLeadingDates: false,
appointmentDisplayCount: 1,
monthCellStyle: MonthCellStyle(
textStyle: TextStyle(fontSize: 19.f),
),
),
onTap: (CalendarTapDetails details) {
_calendarController.selectedDate = details.date;
_onDaySelected(details.date!);
},
),
),
cellBorderColor: AppColors.transparent,
dataSource: MeetingDataSource(_getDataSource()),
monthCellBuilder: (context, details) => Padding(
padding: EdgeInsets.all(12.h),
child: details.date.day.toString().toText14(
isCenter: true,
color: details.date == _calendarController.selectedDate ? AppColors.primaryRedColor : AppColors.textColor,
isEnglishOnly: true,
),
),
// Custom Navigation Arrows
Positioned(
top: 0,
right: isArabic ? null : 0,
left: isArabic ? 0 : null,
child: SizedBox(
height: 60.h,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// Previous Month Arrow
GestureDetector(
onTap: () {
setState(() {
_calendarController.backward!();
});
},
child: Container(
padding: EdgeInsets.all(8.h),
child: Icon(
// isArabic ? Icons.arrow_forward_ios : Icons.arrow_back_ios,
Icons.arrow_back_ios,
size: 24.h,
color: AppColors.primaryRedColor,
),
),
),
// Next Month Arrow
GestureDetector(
onTap: () {
setState(() {
_calendarController.forward!();
});
},
child: Container(
padding: EdgeInsets.all(8.h),
child: Icon(
// isArabic ? Icons.arrow_back_ios : Icons.arrow_forward_ios,
Icons.arrow_forward_ios,
size: 24.h,
color: AppColors.primaryRedColor,
),
),
),
],
),
monthViewSettings: MonthViewSettings(
dayFormat: "EEE",
appointmentDisplayMode: MonthAppointmentDisplayMode.indicator,
showTrailingAndLeadingDates: false,
appointmentDisplayCount: 1,
monthCellStyle: MonthCellStyle(
textStyle: TextStyle(fontSize: 19.f),
),
),
onTap: (CalendarTapDetails details) {
_calendarController.selectedDate = details.date;
_onDaySelected(details.date!);
},
),
),
),
],
),
),
SizedBox(height: 10.h),
@ -164,7 +219,7 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
? ClarityUnmask(
child: ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.15,
maxHeight: MediaQuery.of(context).size.height * 0.25,
minHeight: 0,
),
child: Directionality(
@ -213,15 +268,42 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
),
);
} else {
bookAppointmentsViewModel.getAppointmentNearestGate(
projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID!);
bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime, selectedDateDisplay);
Navigator.of(context).pop();
Navigator.of(context).push(
CustomPageRoute(
page: ReviewAppointmentPage(),
),
);
if (bookAppointmentsViewModel.selectedClinic.clinicID == 17 && bookAppointmentsViewModel.selectedChiefComplaintID == 0) {
GetClinicsListResponseModel selectedClinic = GetClinicsListResponseModel(
clinicID: bookAppointmentsViewModel.selectedClinic.clinicID,
clinicDescription: bookAppointmentsViewModel.selectedClinic.clinicDescription,
isLiveCareClinicAndOnline: false,
liveCareServiceID: 0,
liveCareClinicID: 0);
bookAppointmentsViewModel.setProjectID(bookAppointmentsViewModel.selectedDoctor.projectID.toString());
bookAppointmentsViewModel.setSelectedClinic(selectedClinic);
bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true);
if(appState.isAuthenticated && appState.getAuthenticatedUser()!.age! > 12) {
Navigator.of(context).push(
CustomPageRoute(
page: DentalChiefComplaintsPage(),
),
);
} else {
bookAppointmentsViewModel.getAppointmentNearestGate(projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID!);
bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime, selectedDateDisplay);
Navigator.of(context).pop();
Navigator.of(context).push(
CustomPageRoute(
page: ReviewAppointmentPage(),
),
);
}
} else {
bookAppointmentsViewModel.getAppointmentNearestGate(projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID!);
bookAppointmentsViewModel.setSelectedAppointmentDateTime(selectedDate, selectedTime, selectedDateDisplay);
Navigator.of(context).pop();
Navigator.of(context).push(
CustomPageRoute(
page: ReviewAppointmentPage(),
),
);
}
}
} else {
showCommonBottomSheetWithoutHeight(
@ -231,8 +313,7 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
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),
(LocaleKeys.loginToUseService.tr(context: context)).toText16(color: AppColors.blackColor),
SizedBox(height: 16.h),
@ -331,12 +412,7 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
selectedButtonIndex = 0;
List<Map<String, dynamic>> timeList = [];
for (var i = 0; i < dayEvents.length; i++) {
Map<String, dynamic> timeSlot = {
"isoTime": dayEvents[i].isoTime,
"start": dayEvents[i].start.toString(),
"end": dayEvents[i].end.toString(),
"vidaDate": dayEvents[i].vidaDate
};
Map<String, dynamic> timeSlot = {"isoTime": dayEvents[i].isoTime, "start": dayEvents[i].start.toString(), "end": dayEvents[i].end.toString(), "vidaDate": dayEvents[i].vidaDate};
timeList.add(timeSlot);
}
if (dayEvents.isNotEmpty) {

@ -14,6 +14,7 @@ import 'package:hmg_patient_app_new/features/book_appointments/models/resp_model
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/dental_chief_complaints_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/laser/laser_appointment.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/select_doctor_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -85,7 +86,7 @@ class DoctorCard extends StatelessWidget {
children: [
Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h, applyThemeColor: false),
SizedBox(height: 2.h),
"${isLoading ? 4.78 : doctorsListResponseModel.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true),
"${isLoading ? 4.78 : doctorsListResponseModel.decimalDoctorRate ?? 5.0}".toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true),
],
),
).circle(100).toShimmer2(isShow: isLoading),
@ -144,8 +145,9 @@ class DoctorCard extends StatelessWidget {
spacing: 3.h,
runSpacing: 4.h,
children: [
bookAppointmentsViewModel.isNearestAppointmentSelected
? doctorsListResponseModel.nearestFreeSlot != null
// bookAppointmentsViewModel.isNearestAppointmentSelected
// ?
doctorsListResponseModel.nearestFreeSlot != null
? AppCustomChipWidget(
labelText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot)),
// richText: (isLoading ? "Cardiologist" : DateUtil.getDateStringForNearestSlot(doctorsListResponseModel.nearestFreeSlot))
@ -155,7 +157,8 @@ class DoctorCard extends StatelessWidget {
textColor: AppColors.successColor,
).toShimmer2(isShow: isLoading)
: SizedBox.shrink()
: SizedBox.shrink(),
// : SizedBox.shrink()
,
AppCustomChipWidget(
labelText: "${isLoading ? "Cardiologist" : doctorsListResponseModel.clinicName}",
).toShimmer2(isShow: isLoading),
@ -167,9 +170,10 @@ class DoctorCard extends StatelessWidget {
],
),
),
(doctorsListResponseModel.isLiveCare ?? true)
? SizedBox.shrink()
: Expanded(
// (doctorsListResponseModel.isLiveCare ?? true)
// ? SizedBox.shrink()
// :
Expanded(
flex: 1,
child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).toShimmer2(isShow: isLoading),
),
@ -182,14 +186,26 @@ class DoctorCard extends StatelessWidget {
if (isDoctorNameSearch && doctorsListResponseModel.clinicID == 17) {
GetClinicsListResponseModel selectedClinic = GetClinicsListResponseModel(
clinicID: doctorsListResponseModel.clinicID, clinicDescription: doctorsListResponseModel.clinicName, isLiveCareClinicAndOnline: false, liveCareServiceID: 0, liveCareClinicID: 0);
bookAppointmentsViewModel.setProjectID(doctorsListResponseModel.projectID.toString());
bookAppointmentsViewModel.setSelectedClinic(selectedClinic);
bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true);
Navigator.of(context).push(
CustomPageRoute(
page: DentalChiefComplaintsPage(),
),
);
if (getIt.get<AppState>().isAuthenticated && getIt.get<AppState>().getAuthenticatedUser()!.age! > 12) {
bookAppointmentsViewModel.setProjectID(doctorsListResponseModel.projectID.toString());
bookAppointmentsViewModel.setSelectedClinic(selectedClinic);
bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true);
Navigator.of(context).push(
CustomPageRoute(
page: DentalChiefComplaintsPage(),
),
);
} else {
bookAppointmentsViewModel.setProjectID(doctorsListResponseModel.projectID.toString());
bookAppointmentsViewModel.setSelectedClinic(selectedClinic);
bookAppointmentsViewModel.setIsDoctorsListLoading(true);
Navigator.push(
context,
CustomPageRoute(
page: SelectDoctorPage(),
),
);
}
} else if (isDoctorNameSearch && doctorsListResponseModel.clinicID == 253) {
GetClinicsListResponseModel selectedClinic = GetClinicsListResponseModel(
clinicID: doctorsListResponseModel.clinicID, clinicDescription: doctorsListResponseModel.clinicName, isLiveCareClinicAndOnline: false, liveCareServiceID: 0, liveCareClinicID: 0);
@ -203,6 +219,9 @@ class DoctorCard extends StatelessWidget {
);
} else {
bookAppointmentsViewModel.setSelectedDoctor(doctorsListResponseModel);
GetClinicsListResponseModel selectedClinic = GetClinicsListResponseModel(
clinicID: doctorsListResponseModel.clinicID, clinicDescription: doctorsListResponseModel.clinicName, isLiveCareClinicAndOnline: false, liveCareServiceID: 0, liveCareClinicID: 0);
bookAppointmentsViewModel.setSelectedClinic(selectedClinic);
LoaderBottomSheet.showLoader();
bookAppointmentsViewModel.isLiveCareSchedule
? await bookAppointmentsViewModel.getLiveCareDoctorFreeSlots(
@ -292,9 +311,9 @@ class DoctorCard extends StatelessWidget {
});
}
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
isBold: true,
borderRadius: 10.r,

@ -282,19 +282,22 @@ class _ComprehensiveCheckupPageState extends State<ComprehensiveCheckupPage> {
final String imagePath = appStateLocal.isArabic() ? AppAssets.comprehensiveCheckupAr : AppAssets.comprehensiveCheckupEn;
return Stack(
children: [
Utils.buildImgWithAssets(
icon: imagePath,
width: double.infinity,
fit: BoxFit.cover,
ClipRRect(
borderRadius: BorderRadius.circular(16.r),
child: Utils.buildImgWithAssets(
icon: imagePath,
width: double.infinity,
fit: BoxFit.fitWidth,
),
).paddingAll(16.w),
Align(
alignment: Alignment.topRight,
child: Container(
decoration: BoxDecoration(
color: Color.fromARGB(51, 0, 0, 0),
color: Color.fromARGB(120, 0, 0, 0),
borderRadius: BorderRadius.circular(1000.r),
),
margin: EdgeInsets.all(16.h),
margin: EdgeInsets.all(24.w),
child: IconButton(
icon: Icon(
Icons.zoom_in,

@ -260,7 +260,7 @@ class FeedbackPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.buildImgWithNetwork(
url: contactUsViewModel.patientFeedbackSelectedAppointment!.doctorImageURL!,
url: contactUsViewModel.patientFeedbackSelectedAppointment?.doctorImageURL??"",
iconColor: AppColors.transparent,
width: 63.h,
height: 63.h,
@ -354,11 +354,10 @@ class FeedbackPage extends StatelessWidget {
ImageOptions.showImageOptionsNew(
context,
true,
(String image, file) {
print(image);
print(file);
// Navigator.pop(context);
contactUsViewModel.addFeedbackAttachment(image);
(String base64Image, file) {
// base64Image: The base64-encoded image string
// file: The File object
contactUsViewModel.addFeedbackAttachment(base64Image, file);
},
);
},
@ -385,25 +384,31 @@ class FeedbackPage extends StatelessWidget {
child: ListView.builder(
padding: EdgeInsets.all(16.h),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: contactUsViewModel.feedbackAttachmentList.length,
itemBuilder: (BuildContext context, int index) {
final attachment = contactUsViewModel.feedbackAttachmentList[index];
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
Icons.attach_file,
color: Color(0xff2B353E),
),
SizedBox(width: 8.w),
"Image ${index + 1}".toText14().paddingOnly(bottom: 8.h),
],
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
Icons.attach_file,
color: Color(0xff2B353E),
),
SizedBox(width: 8.w),
Expanded(
child: attachment.fileName.toText14().paddingOnly(bottom: 8.h),
),
],
),
),
Utils.buildSvgWithAssets(icon: AppAssets.cancel_circle_icon).onPress(() {
contactUsViewModel.removeFeedbackAttachment(contactUsViewModel.feedbackAttachmentList[index]);
contactUsViewModel.removeFeedbackAttachment(attachment);
}),
],
);

@ -124,51 +124,67 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "MADA";
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration(
"ER Online Check-In Payment",
emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!,
transID = Utils.getAdvancePaymentTransID(
emergencyServicesViewModel.selectedHospital!.iD,
appState.getAuthenticatedUser()!.patientId!,
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
if (emergencyServicesViewModel.isERBookAppointment) {
await emergencyServicesViewModel.ER_CreateAdvancePayment(
paymentMethodName: selectedPaymentMethod,
paymentReference: transactionData.transactionReference!,
onSuccess: (value) async {
await emergencyServicesViewModel.addAdvanceNumberRequest(
advanceNumber: value,
paymentReference: transactionData.transactionReference!,
appointmentNo: "0",
onSuccess: (val) {
emergencyServicesViewModel.setIsAdvanceERBalanceNeedToBeLoaded(true);
LoaderBottomSheet.hideLoader();
if (emergencyServicesViewModel.isERBookAppointment) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.erAppointmentBookedSuccess.tr(context: context)),
callBackFunc: () {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
},
isFullScreen: false,
isCloseButtonVisible: true,
);
} else {}
});
});
} else {}
}, onError: (errMsg) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: errMsg.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
paytabsViewModel.setPaymentConfiguration("ER Online Check-In Payment", emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!,
ServiceTypeEnum.erOnlineCheckIn.getIdFromServiceEnum(), emergencyServicesViewModel.selectedHospital!.iD);
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: "0",
isScheduleLiveCare: "0",
appointmentDate: "",
appointmentNo: "0",
doctorID: "0",
transactionID: transID,
amount: emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!.toString(),
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
orderDescription: "ER Online Check-In Payment",
serviceID: ServiceTypeEnum.erOnlineCheckIn.getIdFromServiceEnum().toString(),
projectID: emergencyServicesViewModel.selectedHospital!.iD.toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
if (emergencyServicesViewModel.isERBookAppointment) {
await emergencyServicesViewModel.ER_CreateAdvancePayment(
paymentMethodName: selectedPaymentMethod,
paymentReference: transactionData.transactionReference!,
onSuccess: (value) async {
await emergencyServicesViewModel.addAdvanceNumberRequest(
advanceNumber: value,
paymentReference: transactionData.transactionReference!,
appointmentNo: "0",
onSuccess: (val) {
emergencyServicesViewModel.setIsAdvanceERBalanceNeedToBeLoaded(true);
LoaderBottomSheet.hideLoader();
if (emergencyServicesViewModel.isERBookAppointment) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.erAppointmentBookedSuccess.tr(context: context)),
callBackFunc: () {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
},
isFullScreen: false,
isCloseButtonVisible: true,
);
} else {}
});
});
} else {}
},
onError: (errMsg) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: errMsg.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
openPaymentURL("mada");
}
@ -214,51 +230,67 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "VISA";
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration(
"ER Online Check-In Payment",
emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!,
transID = Utils.getAdvancePaymentTransID(
emergencyServicesViewModel.selectedHospital!.iD,
appState.getAuthenticatedUser()!.patientId!,
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
if (emergencyServicesViewModel.isERBookAppointment) {
await emergencyServicesViewModel.ER_CreateAdvancePayment(
paymentMethodName: selectedPaymentMethod,
paymentReference: transactionData.transactionReference!,
onSuccess: (value) async {
await emergencyServicesViewModel.addAdvanceNumberRequest(
advanceNumber: value,
paymentReference: transactionData.transactionReference!,
appointmentNo: "0",
onSuccess: (val) {
emergencyServicesViewModel.setIsAdvanceERBalanceNeedToBeLoaded(true);
LoaderBottomSheet.hideLoader();
if (emergencyServicesViewModel.isERBookAppointment) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.erAppointmentBookedSuccess.tr(context: context)),
callBackFunc: () {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
},
isFullScreen: false,
isCloseButtonVisible: true,
);
} else {}
});
});
} else {}
}, onError: (errMsg) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: errMsg.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
paytabsViewModel.setPaymentConfiguration("ER Online Check-In Payment", emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!,
ServiceTypeEnum.erOnlineCheckIn.getIdFromServiceEnum(), emergencyServicesViewModel.selectedHospital!.iD);
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: "0",
isScheduleLiveCare: "0",
appointmentDate: "",
appointmentNo: "0",
doctorID: "0",
transactionID: transID,
amount: emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!.toString(),
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
orderDescription: "ER Online Check-In Payment",
serviceID: ServiceTypeEnum.erOnlineCheckIn.getIdFromServiceEnum().toString(),
projectID: emergencyServicesViewModel.selectedHospital!.iD.toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
if (emergencyServicesViewModel.isERBookAppointment) {
await emergencyServicesViewModel.ER_CreateAdvancePayment(
paymentMethodName: selectedPaymentMethod,
paymentReference: transactionData.transactionReference!,
onSuccess: (value) async {
await emergencyServicesViewModel.addAdvanceNumberRequest(
advanceNumber: value,
paymentReference: transactionData.transactionReference!,
appointmentNo: "0",
onSuccess: (val) {
emergencyServicesViewModel.setIsAdvanceERBalanceNeedToBeLoaded(true);
LoaderBottomSheet.hideLoader();
if (emergencyServicesViewModel.isERBookAppointment) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.erAppointmentBookedSuccess.tr(context: context)),
callBackFunc: () {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
},
isFullScreen: false,
isCloseButtonVisible: true,
);
} else {}
});
});
} else {}
},
onError: (errMsg) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: errMsg.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
openPaymentURL("visa");
}
@ -298,7 +330,7 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "TAMARA";
openPaymentURL("tamara");
openPaymentURL("TAMARA");
})
: SizedBox.shrink(),
],
@ -319,39 +351,39 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
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: [
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: [
LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true).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,
isBold: true,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
height: 30.h,
).paddingSymmetrical(24.h, 0.h),
],
),
)
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,
isBold: true,
borderRadius: 8,
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
height: 30.h,
).paddingSymmetrical(24.h, 0.h),
],
),
)
: const SizedBox(),
SizedBox(height: 24.h),
LocaleKeys.totalAmountToPay.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.h, 0.h),
@ -369,7 +401,9 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
children: [
LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor),
Utils.getPaymentAmountWithSymbol(
emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientTaxAmount.toString().toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13,
emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientTaxAmount.toString().toText14(isBold: true, color: AppColors.greyTextColor),
AppColors.greyTextColor,
13,
isSaudiCurrency: true),
],
).paddingSymmetrical(24.h, 0.h),
@ -383,14 +417,8 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
],
).paddingSymmetrical(24.h, 0.h),
Platform.isIOS
? Utils.buildSvgWithAssets(
icon: AppAssets.apple_pay_button,
width: 200.h,
height: 80.h,
fit: BoxFit.contain,
applyThemeColor: false
).paddingSymmetrical(24.h, 0.h).onPress(() {
if (Utils.havePrivilege(103)) {
? Utils.buildSvgWithAssets(icon: AppAssets.apple_pay_button, width: 200.h, height: 80.h, fit: BoxFit.contain, applyThemeColor: false).paddingSymmetrical(24.h, 0.h).onPress(() {
if (Utils.havePrivilege(103)) {
startApplePay();
} else {
openPaymentURL("ApplePay");
@ -496,10 +524,8 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
if (appState.isPaytabsEnabled) {
selectedPaymentMethod = "VISA";
LoaderBottomSheet.hideLoader();
paytabsViewModel.setPaymentConfiguration(
"ER Online Check-In Payment",
emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!,
);
paytabsViewModel.setPaymentConfiguration("ER Online Check-In Payment", emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!,
ServiceTypeEnum.erOnlineCheckIn.getIdFromServiceEnum(), emergencyServicesViewModel.selectedHospital!.iD);
paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
if (emergencyServicesViewModel.isERBookAppointment) {

@ -89,7 +89,7 @@ class _HabibWalletState extends State<HabibWalletPage> {
"${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}"
.toText19(isBold: true, color: Colors.white),
"${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId!}"
.toText14(isBold: true, color: AppColors.greyTextColor, isEnglishOnly: true),
.toText14(isBold: true, color: AppColors.greyTextColor),
],
).expanded,
Utils.buildSvgWithAssets(icon: AppAssets.habiblogo, width: 24.h, height: 24.h, applyThemeColor: false),

@ -3,6 +3,7 @@ import 'dart:developer';
import 'dart:io';
import 'package:amazon_payfort/amazon_payfort.dart';
import 'package:clarity_flutter/clarity_flutter.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -54,6 +55,7 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
@override
void initState() {
Clarity.setCurrentScreenName('Habib Wallet Payment Page');
scheduleMicrotask(() {
payfortViewModel.initPayfortViewModel();
});
@ -113,9 +115,24 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "MADA";
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader();
transID = Utils.getAdvancePaymentTransID(habibWalletVM.selectedHospital!.iD!, int.parse(habibWalletVM.fileNumber));
await paytabsViewModel.setPaymentConfiguration(
"Advance Payment", habibWalletVM.walletRechargeAmount, ServiceTypeEnum.advancePayment.getIdFromServiceEnum(), habibWalletVM.selectedHospital!.iD!);
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: "0",
isScheduleLiveCare: "false",
appointmentDate: DateUtil.convertDateToString(DateTime.now()),
appointmentNo: "0",
doctorID: "0",
transactionID: transID,
amount: habibWalletVM.walletRechargeAmount.toString(),
fileNumber: habibWalletVM.fileNumber.toString(),
orderDescription: "Advance Payment",
serviceID: ServiceTypeEnum.advancePayment.getIdFromServiceEnum().toString(),
projectID: habibWalletVM.selectedHospital!.iD!.toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader();
await habibWalletVM.HISCreateAdvancePayment(
paymentMethodName: selectedPaymentMethod,
paidAmount: habibWalletVM.walletRechargeAmount,
@ -162,6 +179,7 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
);
});
}, onError: (err) {
LoaderBottomSheet.showLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err.toString()),
@ -295,9 +313,24 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "VISA";
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader();
transID = Utils.getAdvancePaymentTransID(habibWalletVM.selectedHospital!.iD!, int.parse(habibWalletVM.fileNumber));
await paytabsViewModel.setPaymentConfiguration(
"Advance Payment", habibWalletVM.walletRechargeAmount, ServiceTypeEnum.advancePayment.getIdFromServiceEnum(), habibWalletVM.selectedHospital!.iD!);
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: "0",
isScheduleLiveCare: "false",
appointmentDate: DateUtil.convertDateToString(DateTime.now()),
appointmentNo: "0",
doctorID: "0",
transactionID: transID,
amount: habibWalletVM.walletRechargeAmount.toString(),
fileNumber: habibWalletVM.fileNumber.toString(),
orderDescription: "Advance Payment",
serviceID: ServiceTypeEnum.advancePayment.getIdFromServiceEnum().toString(),
projectID: habibWalletVM.selectedHospital!.iD!.toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader();
await habibWalletVM.HISCreateAdvancePayment(
paymentMethodName: selectedPaymentMethod,
paidAmount: habibWalletVM.walletRechargeAmount,
@ -469,7 +502,7 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
isEnglishOnly: true,
),
AppCustomChipWidget(
labelText: "${habibWalletVM.selectedHospital!.name}",
labelText: "${habibWalletVM.selectedHospital != null ? habibWalletVM.selectedHospital!.name : "Habib Hospital"}",
),
],
).paddingSymmetrical(24.h, 0.h),
@ -480,9 +513,11 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.totalAmountToPay.tr(context: context).toText16(isBold: true),
Utils.getPaymentAmountWithSymbol(
NumberFormat.decimalPattern().format(habibWalletVM.walletRechargeAmount).toString().toText24(isBold: true, isEnglishOnly: true), AppColors.blackColor, 15.h,
isSaudiCurrency: true),
ClarityUnmask(
child: Utils.getPaymentAmountWithSymbol(
NumberFormat.decimalPattern().format(habibWalletVM.walletRechargeAmount).toString().toText24(isBold: true, isEnglishOnly: true), AppColors.blackColor, 15.h,
isSaudiCurrency: true),
),
],
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 12.h),
@ -510,7 +545,11 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
transID = Utils.getAdvancePaymentTransID(habibWalletVM.selectedHospital!.iD!, int.parse(habibWalletVM.fileNumber));
await payfortViewModel.getPayfortConfigurations(serviceId: ServiceTypeEnum.advancePayment.getIdFromServiceEnum(), projectId: habibWalletVM.selectedHospital!.iD!, integrationId: 2);
if(!appState.isPaytabsEnabled) {
await payfortViewModel.getPayfortConfigurations(serviceId: ServiceTypeEnum.advancePayment.getIdFromServiceEnum(), projectId: habibWalletVM.selectedHospital!.iD!, integrationId: 2);
} else {
await paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount, ServiceTypeEnum.advancePayment.getIdFromServiceEnum(), habibWalletVM.selectedHospital!.iD!);
}
applePayInsertRequest.clientRequestID = transID;
applePayInsertRequest.clinicID = 0;
@ -546,12 +585,12 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
applePayInsertRequest.isMobSDK = true;
applePayInsertRequest.merchantReference = transID;
applePayInsertRequest.merchantIdentifier = payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier;
applePayInsertRequest.merchantIdentifier = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.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.signature = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.signature : payfortViewModel.payfortProjectDetailsRespModel!.signature;
applePayInsertRequest.accessCode = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.accessCode : payfortViewModel.payfortProjectDetailsRespModel!.accessCode;
applePayInsertRequest.shaRequestPhrase = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.shaRequest : payfortViewModel.payfortProjectDetailsRespModel!.shaRequest;
applePayInsertRequest.shaResponsePhrase = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.shaResponse : payfortViewModel.payfortProjectDetailsRespModel!.shaResponse;
applePayInsertRequest.returnURL = "";
//TODO: Need to pass dynamic params to the Apple Pay instead of static values
@ -559,7 +598,6 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
if(appState.isPaytabsEnabled) {
selectedPaymentMethod = "VISA";
LoaderBottomSheet.hideLoader();
paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount);
paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader();
await habibWalletVM.HISCreateAdvancePayment(

@ -45,89 +45,110 @@ class _HealthCalculatorDetailedPageState extends State<HealthCalculatorDetailedP
return CollapsingListView(
title: widget.calculatorType.displayName,
bottomChild: widget.calculatorType == HealthCalculatorsTypeEnum.bloodSugar ||
widget.calculatorType == HealthCalculatorsTypeEnum.bloodCholesterol ||
widget.calculatorType == HealthCalculatorsTypeEnum.triglycerides
widget.calculatorType == HealthCalculatorsTypeEnum.bloodCholesterol ||
widget.calculatorType == HealthCalculatorsTypeEnum.triglycerides
? SizedBox()
: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))),
padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h),
child: CustomButton(
text: widget.calculatorType == HealthCalculatorsTypeEnum.bloodSugar ||
widget.calculatorType == HealthCalculatorsTypeEnum.bloodCholesterol ||
widget.calculatorType == HealthCalculatorsTypeEnum.triglycerides
? LocaleKeys.convert.tr(context: context)
: LocaleKeys.calculate.tr(context: context),
onPressed: () {
if (calculatedResult == null) return;
Navigator.of(context).push(CustomPageRoute(
page: SelectClinicPage(calculatorType: widget.calculatorType, calculatedResult: calculatedResult),
));
},
icon: null,
fontSize: 16.f,
backgroundColor: calculatedResult == null ? AppColors.bgRedLightColor : AppColors.primaryRedColor,
borderColor: calculatedResult == null ? AppColors.bgRedLightColor : AppColors.primaryRedColor,
borderRadius: 12.r,
isBold: true)),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor, customBorder: BorderRadius.only(topLeft: Radius.circular(24.r), topRight: Radius.circular(24.r))),
padding: EdgeInsets.symmetric(vertical: 20.h, horizontal: 20.h),
child: CustomButton(
text: widget.calculatorType == HealthCalculatorsTypeEnum.bloodSugar ||
widget.calculatorType == HealthCalculatorsTypeEnum.bloodCholesterol ||
widget.calculatorType == HealthCalculatorsTypeEnum.triglycerides
? LocaleKeys.convert.tr(context: context)
: LocaleKeys.calculate.tr(context: context),
onPressed: () {
if (calculatedResult == null) return;
Navigator.of(context).push(CustomPageRoute(
page: SelectClinicPage(calculatorType: widget.calculatorType, calculatedResult: calculatedResult),
));
},
icon: null,
fontSize: 16.f,
backgroundColor: calculatedResult == null ? AppColors.bgRedLightColor : AppColors.primaryRedColor,
borderColor: calculatedResult == null ? AppColors.bgRedLightColor : AppColors.primaryRedColor,
borderRadius: 12.r,
isBold: true)),
child: getCalculatorWidget(
type: widget.calculatorType,
onCalculate: (result) {
// result may be directly provided by widget, but prefer provider maps if available
switch (widget.calculatorType) {
case HealthCalculatorsTypeEnum.bmi:
calculatedResult = provider.bmiResultMap != null
? {...?provider.bmiResultMap, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: {'result': result, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID};
break;
case HealthCalculatorsTypeEnum.calories:
case HealthCalculatorsTypeEnum.bmr:
calculatedResult = provider.caloriesResultMap != null
? {...?provider.caloriesResultMap, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: {'result': result, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID};
break;
case HealthCalculatorsTypeEnum.idealBodyWeight:
calculatedResult = provider.ibwResultMap != null
? {...?provider.ibwResultMap, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: {'result': result, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID};
break;
case HealthCalculatorsTypeEnum.bodyFat:
calculatedResult = provider.bodyFatResultMap != null
? {...?provider.bodyFatResultMap, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: {'result': result, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID};
break;
case HealthCalculatorsTypeEnum.crabsProteinFat:
calculatedResult = provider.macrosResultMap != null
? {...?provider.macrosResultMap, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: {'result': result, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID};
break;
case HealthCalculatorsTypeEnum.ovulation:
calculatedResult = (provider.ovulationResult is Map)
? {...provider.ovulationResult as Map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: {'result': provider.ovulationResult ?? result, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID};
break;
case HealthCalculatorsTypeEnum.deliveryDueDate:
calculatedResult = (provider.deliveryResult is Map)
? {...provider.deliveryResult as Map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: {'result': provider.deliveryResult ?? result, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID};
break;
case HealthCalculatorsTypeEnum.bloodSugar:
calculatedResult = (provider.bloodSugarResult is Map)
? {...provider.bloodSugarResult as Map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: {'result': provider.bloodSugarResult ?? result, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID};
break;
case HealthCalculatorsTypeEnum.bloodCholesterol:
calculatedResult = (provider.bloodCholesterolResult is Map)
? {...provider.bloodCholesterolResult as Map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: {'result': provider.bloodCholesterolResult ?? result, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID};
break;
case HealthCalculatorsTypeEnum.triglycerides:
calculatedResult = (provider.triglyceridesResult is Map)
? {...provider.triglyceridesResult as Map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: {'result': provider.triglyceridesResult ?? result, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID};
break;
}
setState(() {
switch (widget.calculatorType) {
case HealthCalculatorsTypeEnum.bmi:
final map = provider.bmiResultMap ?? (result is Map ? Map<String, dynamic>.from(result) : null);
calculatedResult = map != null
? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: null;
break;
case HealthCalculatorsTypeEnum.calories:
case HealthCalculatorsTypeEnum.bmr:
final map = provider.caloriesResultMap ?? (result is Map ? Map<String, dynamic>.from(result) : null);
calculatedResult = map != null
? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: null;
break;
case HealthCalculatorsTypeEnum.idealBodyWeight:
final map = provider.ibwResultMap ?? (result is Map ? Map<String, dynamic>.from(result) : null);
calculatedResult = map != null
? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: null;
break;
case HealthCalculatorsTypeEnum.bodyFat:
final map = provider.bodyFatResultMap ?? (result is Map ? Map<String, dynamic>.from(result) : null);
calculatedResult = map != null
? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: null;
break;
case HealthCalculatorsTypeEnum.crabsProteinFat:
final map = provider.macrosResultMap ?? (result is Map ? Map<String, dynamic>.from(result) : null);
calculatedResult = map != null
? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: null;
break;
case HealthCalculatorsTypeEnum.ovulation:
final map = (provider.ovulationResult is Map)
? provider.ovulationResult
: (result is Map ? Map<String, dynamic>.from(result) : null);
calculatedResult = map != null
? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: null;
break;
case HealthCalculatorsTypeEnum.deliveryDueDate:
final map = (provider.deliveryResult is Map)
? provider.deliveryResult
: (result is Map ? Map<String, dynamic>.from(result) : null);
calculatedResult = map != null
? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: null;
break;
case HealthCalculatorsTypeEnum.bloodSugar:
final map = (provider.bloodSugarResult is Map)
? provider.bloodSugarResult
: (result is Map ? Map<String, dynamic>.from(result) : null);
calculatedResult = map != null
? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: null;
break;
case HealthCalculatorsTypeEnum.bloodCholesterol:
final map = (provider.bloodCholesterolResult is Map)
? provider.bloodCholesterolResult
: (result is Map ? Map<String, dynamic>.from(result) : null);
calculatedResult = map != null
? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: null;
break;
case HealthCalculatorsTypeEnum.triglycerides:
final map = (provider.triglyceridesResult is Map)
? provider.triglyceridesResult
: (result is Map ? Map<String, dynamic>.from(result) : null);
calculatedResult = map != null
? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID}
: null;
break;
}
});
},
).paddingSymmetrical(20.w, 24.h),
);
@ -140,68 +161,68 @@ class _HealthCalculatorDetailedPageState extends State<HealthCalculatorDetailedP
case HealthCalculatorsTypeEnum.bmi:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BMIWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.calories:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: CaloriesWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.bmr:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BMRWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.idealBodyWeight:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: IdealBodyWeightWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.bodyFat:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BodyFatWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.crabsProteinFat:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: CrabsWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.ovulation:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: OvulationWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.deliveryDueDate:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: DeliveryDueDWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.bloodSugar:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BloodSugarWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.bloodCholesterol:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: BloodCholesterolWidget(onChange: onCalculate),
);
case HealthCalculatorsTypeEnum.triglycerides:
return Container(
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))),
child: TriglyceridesWidget(onChange: onCalculate),
);
}

@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
@ -13,8 +14,10 @@ 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/blood_donation/blood_donation_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/hmg_services/models/ui_models/hmg_services_component_model.dart';
import 'package:hmg_patient_app_new/features/hospital/hospital_selection_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart';
import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart';
@ -45,6 +48,7 @@ import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../core/dependencies.dart';
import '../emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart';
class ServicesPage extends StatefulWidget {
final bool showBackIcon;
@ -116,51 +120,51 @@ class _ServicesPageState extends State<ServicesPage> {
);
}
}),
// HmgServicesComponentModel(
// 11,
// LocaleKeys.indoorNavigation.tr(),
// "",
// AppAssets.indoor_nav_icon,
// bgColor: Color(0xff45A2F8),
// true,
// route: null,
// onTap: () {
// showCommonBottomSheetWithoutHeight(
// title: LocaleKeys.selectHospital.tr(),
// GetIt.instance<NavigationService>().navigatorKey.currentContext!,
// child: ChangeNotifierProvider(
// create: (context) => HospitalSelectionBottomSheetViewModel(getIt()),
// child: Consumer<HospitalSelectionBottomSheetViewModel>(
// builder: (_, vm, __) => HospitalBottomSheetBody(
// searchText: vm.searchController,
// displayList: vm.displayList,
// onFacilityClicked: (value) {
// vm.setSelectedFacility(value);
// vm.getDisplayList();
// },
// onHospitalClicked: (hospital) {
// Navigator.pop(GetIt.instance<NavigationService>().navigatorKey.currentContext!);
// vm.openPenguin(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: () {
// GetIt.instance<NavigationService>().navigatorKey.currentContext!.read<EmergencyServicesViewModel>().clearSearchText();
// },
// );
// },
// ),
HmgServicesComponentModel(
11,
LocaleKeys.indoorNavigation.tr(),
"",
AppAssets.indoor_nav_icon,
bgColor: Color(0xff45A2F8),
true,
route: null,
onTap: () {
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.selectHospital.tr(),
GetIt.instance<NavigationService>().navigatorKey.currentContext!,
child: ChangeNotifierProvider(
create: (context) => HospitalSelectionBottomSheetViewModel(getIt()),
child: Consumer<HospitalSelectionBottomSheetViewModel>(
builder: (_, vm, __) => HospitalBottomSheetBody(
searchText: vm.searchController,
displayList: vm.displayList,
onFacilityClicked: (value) {
vm.setSelectedFacility(value);
vm.getDisplayList();
},
onHospitalClicked: (hospital) {
Navigator.pop(GetIt.instance<NavigationService>().navigatorKey.currentContext!);
vm.openPenguin(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: () {
GetIt.instance<NavigationService>().navigatorKey.currentContext!.read<EmergencyServicesViewModel>().clearSearchText();
},
);
},
),
HmgServicesComponentModel(
11, LocaleKeys.eReferralServices.tr(), "", AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async {
if (getIt.get<AppState>().isAuthenticated) {

@ -9,192 +9,190 @@ import 'package:hmg_patient_app_new/theme/colors.dart';
class LandingPageData {
static List<ServiceCardData> get getNotLoggedInServiceCardsList => [
ServiceCardData(
serviceName: "emergency",
icon: AppAssets.emergency_services_icon,
title: getIt.get<AppState>().isArabic()?LocaleKeys.services3 :LocaleKeys.emergency2,
subtitle: getIt.get<AppState>().isArabic()?LocaleKeys.emergency2:LocaleKeys.services3,
backgroundColor: AppColors.primaryRedColor,
iconColor: Colors.white,
textColor: Colors.white,
isBold: true,
),
ServiceCardData(
serviceName: "search_doctor",
icon: AppAssets.search_doctor_icon,
title: LocaleKeys.finda,
subtitle: LocaleKeys.doctor,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "health_calculators_and_converts",
icon: AppAssets.health_calculators_icon,
title: getIt.get<AppState>().isArabic()? LocaleKeys.calculators:LocaleKeys.health ,
subtitle: getIt.get<AppState>().isArabic()? LocaleKeys.health:LocaleKeys.calculators,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "health_converters",
icon: AppAssets.health_converters_icon,
title: getIt.get<AppState>().isArabic()? LocaleKeys.converters:LocaleKeys.health,
subtitle: getIt.get<AppState>().isArabic()? LocaleKeys.health:LocaleKeys.converters,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
// ServiceCardData(
// serviceName: "parking_guide",
// icon: AppAssets.car_parking_icon,
// title: getIt.get<AppState>().isArabic()? LocaleKeys.guide:LocaleKeys.parking2,
// subtitle: getIt.get<AppState>().isArabic()? LocaleKeys.parking2:LocaleKeys.guide,
// backgroundColor: AppColors.whiteColor,
// iconColor: AppColors.blackColor,
// textColor: AppColors.blackColor,
// isBold: false,
// ),
ServiceCardData(
serviceName: "indoor_navigation",
icon: AppAssets.indoor_nav_icon,
title: LocaleKeys.indoor,
subtitle: LocaleKeys.thehospital,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
];
ServiceCardData(
serviceName: "emergency",
icon: AppAssets.emergency_services_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.services3 : LocaleKeys.emergency2,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.emergency2 : LocaleKeys.services3,
backgroundColor: AppColors.primaryRedColorBase,
iconColor: Colors.white,
textColor: Colors.white,
isBold: true,
),
ServiceCardData(
serviceName: "search_doctor",
icon: AppAssets.search_doctor_icon,
title: LocaleKeys.finda,
subtitle: LocaleKeys.doctor,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "health_calculators_and_converts",
icon: AppAssets.health_calculators_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.calculators : LocaleKeys.health,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.health : LocaleKeys.calculators,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "health_converters",
icon: AppAssets.health_converters_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.converters : LocaleKeys.health,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.health : LocaleKeys.converters,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
// ServiceCardData(
// serviceName: "parking_guide",
// icon: AppAssets.car_parking_icon,
// title: getIt.get<AppState>().isArabic()? LocaleKeys.guide:LocaleKeys.parking2,
// subtitle: getIt.get<AppState>().isArabic()? LocaleKeys.parking2:LocaleKeys.guide,
// backgroundColor: AppColors.whiteColor,
// iconColor: AppColors.blackColor,
// textColor: AppColors.blackColor,
// isBold: false,
// ),
ServiceCardData(
serviceName: "indoor_navigation",
icon: AppAssets.indoor_nav_icon,
title: LocaleKeys.indoor,
subtitle: LocaleKeys.thehospital,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
];
List<ServiceCardData> get getLoggedInServiceCardsList => [
ServiceCardData(
serviceName: "emergency",
icon: AppAssets.emergency_services_icon,
title: getIt.get<AppState>().isArabic()?LocaleKeys.services3 :LocaleKeys.emergency2,
subtitle: getIt.get<AppState>().isArabic()?LocaleKeys.emergency2:LocaleKeys.services3,
backgroundColor: AppColors.primaryRedColor,
iconColor: Colors.white,
textColor: Colors.white,
isBold: true,
),
ServiceCardData(
serviceName: "lab_results",
icon: AppAssets.home_lab_result_icon,
title: getIt.get<AppState>().isArabic()?LocaleKeys.labSubtitle:LocaleKeys.lab,
subtitle: getIt.get<AppState>().isArabic()?LocaleKeys.lab:LocaleKeys.labSubtitle ,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "radiology_results",
icon: AppAssets.my_radiology_icon,
title: getIt.get<AppState>().isArabic()?LocaleKeys.labSubtitle:LocaleKeys.radiology,
subtitle: getIt.get<AppState>().isArabic()?LocaleKeys.radiology:LocaleKeys.labSubtitle ,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "prescriptions",
icon: AppAssets.my_prescription_icon,
title: getIt.get<AppState>().isArabic()?LocaleKeys.medicinesSubtitle:LocaleKeys.medicalKey,
subtitle: getIt.get<AppState>().isArabic()?LocaleKeys.medicalKey:LocaleKeys.medicinesSubtitle,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "insurance_update",
icon: AppAssets.insurance_update_icon,
title: getIt.get<AppState>().isArabic()?LocaleKeys.insurance :LocaleKeys.insurance,
subtitle: getIt.get<AppState>().isArabic()?LocaleKeys.medical: LocaleKeys.details2,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "my_doctors",
icon: AppAssets.my_doctors_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.myDoctorSubtitle2.tr() : "My",
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.myDoctor.tr() : "Doctors",
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "sick_leaves",
icon: AppAssets.my_sick_leave_icon,
title: getIt.get<AppState>().isArabic()?LocaleKeys.sickSubtitle:LocaleKeys.sick2,
subtitle: getIt.get<AppState>().isArabic()?LocaleKeys.sick2:LocaleKeys.sickSubtitle,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "my_invoice",
icon: AppAssets.invoiceListIcon,
title: getIt.get<AppState>().isArabic()?LocaleKeys.myDoctorSubtitle2:LocaleKeys.invoiceList2,
subtitle: getIt.get<AppState>().isArabic()?LocaleKeys.invoiceList2:LocaleKeys.myDoctorSubtitle2,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
];
ServiceCardData(
serviceName: "emergency",
icon: AppAssets.emergency_services_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.services3 : LocaleKeys.emergency2,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.emergency2 : LocaleKeys.services3,
backgroundColor: AppColors.primaryRedColorBase,
iconColor: Colors.white,
textColor: Colors.white,
isBold: true,
),
ServiceCardData(
serviceName: "lab_results",
icon: AppAssets.home_lab_result_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.labSubtitle : LocaleKeys.lab,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.lab : LocaleKeys.labSubtitle,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "radiology_results",
icon: AppAssets.my_radiology_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.labSubtitle : LocaleKeys.radiology,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.radiology : LocaleKeys.labSubtitle,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "prescriptions",
icon: AppAssets.my_prescription_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.medicinesSubtitle : LocaleKeys.medicalKey,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.medicalKey : LocaleKeys.medicinesSubtitle,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "insurance_update",
icon: AppAssets.insurance_update_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.insurance : LocaleKeys.insurance,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.medical : LocaleKeys.details2,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "my_doctors",
icon: AppAssets.my_doctors_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.myDoctorSubtitle2.tr() : "My",
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.myDoctor.tr() : "Doctors",
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "sick_leaves",
icon: AppAssets.my_sick_leave_icon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.sickSubtitle : LocaleKeys.sick2,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.sick2 : LocaleKeys.sickSubtitle,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "my_invoice",
icon: AppAssets.invoiceListIcon,
title: getIt.get<AppState>().isArabic() ? LocaleKeys.myDoctorSubtitle2 : LocaleKeys.invoiceList2,
subtitle: getIt.get<AppState>().isArabic() ? LocaleKeys.invoiceList2 : LocaleKeys.myDoctorSubtitle2,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
];
static List<ServiceCardData> get getServiceCardsList => [
ServiceCardData(
serviceName: "livecare",
icon: AppAssets.small_livecare_icon,
title: LocaleKeys.liveCare,
subtitle: LocaleKeys.liveCareServiceDesc,
largeCardIcon: AppAssets.liveCareService,
backgroundColor: AppColors.successColor,
iconColor: AppColors.whiteColor,
isBold: false,
isPNG: false
),
// ServiceCardData(
// icon: AppAssets.homeBottom,
// title: "Dermatology",
// subtitle: "Explore our app, View our services and offers",
// largeCardIcon: AppAssets.homeBottom,
// backgroundColor: AppColors.primaryRedColor,
// isBold: false,
// ),
ServiceCardData(
serviceName: "home_health_care",
icon: AppAssets.homeBottom,
title: LocaleKeys.homeHealthCare,
subtitle: LocaleKeys.homeHealthCareText,
largeCardIcon: AppAssets.homeHealthCareService,
backgroundColor: AppColors.primaryRedColor,
iconColor: AppColors.whiteColor,
isBold: false,
isPNG: false
),
ServiceCardData(
serviceName: "pharmacy",
icon: AppAssets.defaultMEP, //359846
title: LocaleKeys.hmgPharmacy,
subtitle: LocaleKeys.hmgPharmacyText,
largeCardIcon: AppAssets.pharmacyService,
backgroundColor: AppColors.pharmacyBGColor,
iconColor: null,
isBold: true,
isPNG: true
),
];
ServiceCardData(
serviceName: "livecare",
icon: AppAssets.small_livecare_icon,
title: LocaleKeys.liveCare,
subtitle: LocaleKeys.liveCareServiceDesc,
largeCardIcon: AppAssets.liveCareService,
backgroundColor: AppColors.successColor,
iconColor: AppColors.whiteColor,
isBold: false,
isPNG: false),
// ServiceCardData(
// icon: AppAssets.homeBottom,
// title: "Dermatology",
// subtitle: "Explore our app, View our services and offers",
// largeCardIcon: AppAssets.homeBottom,
// backgroundColor: AppColors.primaryRedColor,
// isBold: false,
// ),
ServiceCardData(
serviceName: "home_health_care",
icon: AppAssets.homeBottom,
title: LocaleKeys.homeHealthCare,
subtitle: LocaleKeys.homeHealthCareText,
largeCardIcon: AppAssets.homeHealthCareService,
backgroundColor: AppColors.primaryRedColor,
iconColor: AppColors.whiteColor,
isBold: false,
isPNG: false),
ServiceCardData(
serviceName: "pharmacy",
icon: AppAssets.defaultMEP,
//359846
title: LocaleKeys.hmgPharmacy,
subtitle: LocaleKeys.hmgPharmacyText,
largeCardIcon: AppAssets.pharmacyService,
backgroundColor: AppColors.pharmacyBGColor,
iconColor: null,
isBold: true,
isPNG: true),
];
}

@ -17,6 +17,7 @@ import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import 'dart:ui' as ui;
class LakumWalletDetails extends StatelessWidget {
const LakumWalletDetails({super.key});
@ -239,10 +240,10 @@ class LakumWalletDetails extends StatelessWidget {
return CustomButton(
height: 46.h,
icon: AppAssets.redeem_icon,
iconColor: AppColors.whiteColor,
iconColor: AppColors.textColor,
iconSize: 24.h,
backgroundColor: AppColors.habibPharmacyColor,
textColor: AppColors.whiteColor,
textColor: AppColors.textColor,
text: LocaleKeys.redeem.tr(context: context),
borderWidth: 0.w,
fontWeight: FontWeight.w600,
@ -350,7 +351,10 @@ class LakumWalletDetails extends StatelessWidget {
maxlines: 2,
),
SizedBox(height: 2.h),
formattedDate.toText12(color: AppColors.greyTextColor),
Directionality(
textDirection: ui.TextDirection.ltr,
child: formattedDate.toText12(color: AppColors.greyTextColor),
),
],
),
),
@ -363,10 +367,10 @@ class LakumWalletDetails extends StatelessWidget {
isBold: true,
color: transactionColor,
),
if (transaction.amount != null && transaction.amount! > 0)
"${transaction.amount?.toStringAsFixed(2) ?? "0.00"} SAR".toText11(
color: AppColors.greyTextColor,
),
// if (transaction.amount != null && transaction.amount! > 0)
// "${transaction.amount?.toStringAsFixed(2) ?? "0.00"} SAR".toText11(
// color: AppColors.greyTextColor,
// ),
],
),
],

@ -5,6 +5,7 @@ import 'dart:ui' as ui;
import 'package:clarity_flutter/clarity_flutter.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:flutter_swiper_view/flutter_swiper_view.dart';
import 'package:get_it/get_it.dart';
@ -31,6 +32,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_queue_details_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart';
import 'package:hmg_patient_app_new/features/occasion_theme/occasion_theme_view_model.dart';
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart';
import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
@ -49,6 +51,7 @@ import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dar
import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/lakum_wallet_card.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/occasion_banner_widget.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dart';
import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.dart';
@ -58,6 +61,7 @@ import 'package:hmg_patient_app_new/presentation/notifications/notifications_lis
import 'package:hmg_patient_app_new/presentation/offers_and_discounts/offers_and_discounts_page.dart';
import 'package:hmg_patient_app_new/presentation/offers_and_discounts/widgets/offers_and_discounts.dart';
import 'package:hmg_patient_app_new/presentation/rate_appointment/rate_appointment_doctor.dart';
import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_screen.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/ancillary_procedures_details_page.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.dart';
@ -120,12 +124,20 @@ class _LandingPageState extends State<LandingPage> {
authVM = context.read<AuthenticationViewModel>();
habibWalletVM = context.read<HabibWalletViewModel>();
appointmentRatingViewModel = context.read<AppointmentRatingViewModel>();
appState = getIt.get<AppState>();
authVM.savePushTokenToAppState();
if (mounted) {
authVM.checkLastLoginStatus(() {
showQuickLogin(context);
});
final user = appState.getAuthenticatedUser();
final hasUserQuickLoginData = user != null && (user.mobileNumber?.isNotEmpty ?? false) && (user.patientIdentificationNo?.isNotEmpty ?? false);
if (hasUserQuickLoginData) {
authVM.checkLastLoginStatus(() {
if (mounted) {
showQuickLogin(context);
}
});
}
}
_scrollController.addListener(() {
@ -147,18 +159,23 @@ class _LandingPageState extends State<LandingPage> {
if (appState.isAuthenticated) {
Clarity.setCustomUserId(appState.getAuthenticatedUser()!.patientId.toString());
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
habibWalletVM.getLakumAccountInformation();
// habibWalletVM.initHabibWalletProvider();
// Wallet & Lakum data are fetched lazily when the user unhides the values.
// habibWalletVM.getPatientBalanceAmount();
// habibWalletVM.getLakumAccountInformation();
todoSectionViewModel.initializeTodoSectionViewModel();
immediateLiveCareViewModel.initImmediateLiveCare();
immediateLiveCareViewModel.getPatientLiveCareHistory();
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
myAppointmentsViewModel.getPatientAppointments(true, false, isForTimeLine: true);
emergencyServicesViewModel.checkPatientERAdvanceBalance();
// myAppointmentsViewModel.getPatientAppointmentQueueDetails();
notificationsViewModel.initNotificationsViewModel();
insuranceViewModel.initInsuranceProvider();
if (Utils.havePrivilege(122)) {
insuranceViewModel.initInsuranceProvider(isFromHomePage: true);
}
if (!appState.isRatedVisible) {
appointmentRatingViewModel.getLastRatingAppointment(
@ -172,12 +189,19 @@ class _LandingPageState extends State<LandingPage> {
appointmentNo,
projectID,
onSuccess: ((response) {
// Only open dialog if details are loaded AND widget is mounted
if (!mounted || appointmentRatingViewModel.appointmentDetails == null) return;
appointmentRatingViewModel.setClinicOrDoctor(false);
appointmentRatingViewModel.setTitle(LocaleKeys.rateDoctor.tr(context: context));
appointmentRatingViewModel.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context));
appointmentRatingViewModel.setTitle(LocaleKeys.rateDoctor.tr());
appointmentRatingViewModel.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr());
openLastRating();
appState.setRatedVisible(true);
}),
onError: (String errorMessage) {
// Silently mark as rated to prevent retry loops
if (mounted) appState.setRatedVisible(true);
},
);
}
},
@ -205,19 +229,29 @@ class _LandingPageState extends State<LandingPage> {
body: Consumer<InsuranceViewModel>(builder: (context, insuranceVM, child) {
return Stack(
children: [
// Renders nothing unless a remote-config occasion theme (e.g.
// National Day) is currently active — fills the whole page,
// behind all other content below.
const OccasionBackgroundWidget(),
RefreshIndicator(
color: AppColors.primaryRedColor,
onRefresh: () async {
// Re-check Remote Config for occasion theme changes on
// pull-to-refresh (independent of auth state) — handy for
// testing without needing to restart the app.
unawaited(GetIt.instance<OccasionThemeViewModel>().refresh());
if (appState.isAuthenticated) {
// Refresh Appointments Data
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
myAppointmentsViewModel.getPatientAppointments(true, false, isForTimeLine: true);
// Refresh Appointments Data
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
habibWalletVM.getLakumAccountInformation();
// habibWalletVM.initHabibWalletProvider();
// habibWalletVM.getPatientBalanceAmount();
// habibWalletVM.getLakumAccountInformation();
// Refresh Ancillary Orders
todoSectionViewModel.setIsAncillaryOrdersNeedReloading(true);
@ -244,7 +278,6 @@ class _LandingPageState extends State<LandingPage> {
// }
// },
// );
}
},
child: SingleChildScrollView(
@ -253,11 +286,11 @@ class _LandingPageState extends State<LandingPage> {
padding: EdgeInsets.only(
top: (appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown)
? (MediaQuery.paddingOf(context).top + 70.h)
: kToolbarHeight + 0.h,
bottom: 24),
: kToolbarHeight + 16.h,
bottom: 24.h),
child: Column(
spacing: 16.h,
children: [
const OccasionBannerWidget(),
Row(
spacing: 8.h,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -273,6 +306,15 @@ class _LandingPageState extends State<LandingPage> {
page: FamilyMedicalScreen(),
),
);
// Navigator.pushReplacementNamed(
// // context,
// GetIt.instance<NavigationService>().navigatorKey.currentContext!,
// AppRoutes.zoomCallPage,
// // arguments: CallArguments(appointmentID, "111", "Patient", "40", "1", true, 1),
// arguments: CallArguments("yosemite-338", "123", "Patient", "40", "0", true, 1),
// // arguments: CallArguments("SmallDailyStandup9875", "123", "Patient", "40", "0", false, int.parse(widget.incomingCallData!.appointmentNo!)),
// );
},
name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'),
imageWidget: Selector<ProfileSettingsViewModel, String?>(
@ -296,6 +338,13 @@ class _LandingPageState extends State<LandingPage> {
onPressed: () async {
await authVM.onLoginPressed();
// Navigator.pushReplacementNamed(
// // context,
// GetIt.instance<NavigationService>().navigatorKey.currentContext!,
// AppRoutes.zoomCallPage,
// arguments: CallArguments("lake-tahoe-289", "123", "Patient", "40", "0", true, 1),
// );
// Navigator.of(context).push(
// CustomPageRoute(
// // page: NotificationsListPage(),
@ -308,11 +357,11 @@ class _LandingPageState extends State<LandingPage> {
// ...existing commented code...
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
fontWeight: FontWeight.w700,
borderRadius: 12.r,
borderRadius: 10.r,
padding: EdgeInsets.fromLTRB(12.h, 0, 12.h, 0),
height: 40.h,
),
@ -322,21 +371,7 @@ class _LandingPageState extends State<LandingPage> {
// spacing: 18.h,
children: [
Stack(clipBehavior: Clip.none, children: [
if (appState.isAuthenticated)
Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 24.h, width: 24.h).onPress(() async {
if (appState.isAuthenticated) {
notificationsViewModel.setNotificationStatusID(2);
notificationsViewModel.getAllNotifications();
Navigator.of(context).push(
CustomPageRoute(
page: NotificationsListPage(),
// page: LoginScreen(),
),
);
} else {
await authVM.onLoginPressed();
}
}),
if (appState.isAuthenticated) Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 24.h, width: 24.h),
(appState.isAuthenticated && todoSectionVM.notificationsCount != null && todoSectionVM.notificationsCount != "0")
? Positioned(
// right: appState.isArabic() ? 8.w : -8.w,
@ -348,7 +383,7 @@ class _LandingPageState extends State<LandingPage> {
height: 18.h,
padding: EdgeInsets.all(2),
decoration: BoxDecoration(
color: AppColors.primaryRedColor,
color: AppColors.primaryRedColorBase,
borderRadius: BorderRadius.circular(20.r),
),
child: Text(
@ -364,7 +399,20 @@ class _LandingPageState extends State<LandingPage> {
),
)
: SizedBox.shrink(),
]),
]).onPress(() async {
if (appState.isAuthenticated) {
notificationsViewModel.setNotificationStatusID(2);
notificationsViewModel.getAllNotifications();
Navigator.of(context).push(
CustomPageRoute(
page: NotificationsListPage(),
// page: LoginScreen(),
),
);
} else {
await authVM.onLoginPressed();
}
}),
SizedBox(
width: 24.w,
),
@ -401,6 +449,7 @@ class _LandingPageState extends State<LandingPage> {
}),
],
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 16.h),
!appState.isAuthenticated
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
@ -446,6 +495,7 @@ class _LandingPageState extends State<LandingPage> {
),
).paddingSymmetrical(24.w, 0.h)
: SizedBox.shrink(),
SizedBox(height: 16.h),
// Offers And Discounts Carousel - Auto-scrolling from right to left
// appState.isAuthenticated && appState.isEnabledOffersAndDiscountsCarousel
@ -493,8 +543,8 @@ class _LandingPageState extends State<LandingPage> {
).paddingSymmetrical(24.h, 0.h).onPress(() {
myAppointmentsViewModel.onTabChange(0);
myAppointmentsViewModel.updateListWRTTab(0);
Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage()));
Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage()));
// getIt.get<PayTabsViewModel>().setPaymentConfiguration("Appointment Payment", 15.5);
// getIt.get<PayTabsViewModel>().startCardPayment();
}),
@ -634,9 +684,9 @@ class _LandingPageState extends State<LandingPage> {
getIt.get<BookAppointmentsViewModel>().onTabChanged(0);
Navigator.of(context).push(CustomPageRoute(page: BookAppointmentPage()));
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
fontWeight: FontWeight.w600,
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),
@ -868,6 +918,7 @@ class _LandingPageState extends State<LandingPage> {
],
),
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -883,6 +934,7 @@ class _LandingPageState extends State<LandingPage> {
}),
],
).paddingSymmetrical(24.w, 0.h),
SizedBox(height: 16.h),
ConstrainedBox(
constraints: BoxConstraints(maxHeight: isFoldable ? 450.h : (isTablet ? 440.h : 431.h), minHeight: 411.h),
child: ListView.separated(
@ -912,13 +964,14 @@ class _LandingPageState extends State<LandingPage> {
separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 16.w),
),
),
SizedBox(height: 16.h),
appState.isAuthenticated
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.myBalanceSubtitle.tr(context: context).toText16(isBold: true).paddingSymmetrical(24.h, 0.h),
SizedBox(
height: 175.h,
height: 180.h,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: 2,
@ -955,13 +1008,13 @@ class _LandingPageState extends State<LandingPage> {
),
),
),
(appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown)
(appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown)
? Container(
height: MediaQuery.paddingOf(context).top + 50.h,
decoration: ShapeDecoration(
color: AppColors.secondaryLightRedBorderColor,
color: AppColors.secondaryLightRedColorBase,
shape: SmoothRectangleBorder(
side: BorderSide(width: 1, color: AppColors.primaryRedColor.withAlpha(20)),
side: BorderSide(width: 1, color: AppColors.primaryRedColorBase.withAlpha(20)),
// borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)),
smoothness: 1,
),
@ -972,7 +1025,7 @@ class _LandingPageState extends State<LandingPage> {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true).paddingSymmetrical(0.h, 0.h),
LocaleKeys.insuranceExpiredOrInactive.tr(context: context).toText14(color: AppColors.primaryRedColorBase, isBold: true).paddingSymmetrical(0.h, 0.h),
Row(
children: [
CustomButton(
@ -984,8 +1037,8 @@ class _LandingPageState extends State<LandingPage> {
showCommonBottomSheetWithoutHeight(context,
child: PatientInsuranceCardUpdateCard(), callBackFunc: () {}, title: "", isCloseButtonVisible: false, isFullScreen: false);
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.secondaryLightRedBorderColor,
backgroundColor: AppColors.primaryRedColorBase,
borderColor: AppColors.primaryRedColorBase,
textColor: Colors.white,
fontSize: 12.f,
fontWeight: FontWeight.bold,
@ -993,7 +1046,7 @@ class _LandingPageState extends State<LandingPage> {
padding: EdgeInsets.fromLTRB(15, 0, 15, 0),
height: 36.h,
).paddingSymmetrical(12.h, 0.h),
Icon(Icons.close, color: AppColors.primaryRedColor).onPress(() {
Icon(Icons.close, color: AppColors.primaryRedColorBase).onPress(() {
insuranceVM.setIsInsuranceExpiryBannerShown(false);
}),
],
@ -1415,6 +1468,7 @@ class _LandingPageState extends State<LandingPage> {
appointmentNoVida: order.appointmentNo ?? 0,
orderNo: order.orderNo ?? 0,
projectID: order.projectID ?? 0,
clinicID: 0,
projectName: order.projectName ?? "",
setupID: order.setupID ?? "",
),
@ -1458,6 +1512,7 @@ class _LandingPageState extends State<LandingPage> {
}
openLastRating() {
if (!mounted) return;
showCommonBottomSheetWithoutHeight(
context,
titleWidget: Selector<AppointmentRatingViewModel, String?>(

@ -59,26 +59,19 @@ class HabibWalletCard extends StatelessWidget {
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
LocaleKeys.habibWallet.tr(context: context).toText16(isBold: true, letterSpacing: -0.2),
// Container(
// height: 40.h,
// width: 40.h,
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
// color: AppColors.textColor,
// borderRadius: 8.h,
// ),
// child: Padding(
// padding: EdgeInsets.all(8.h),
// child: Utils.buildSvgWithAssets(
// icon: AppAssets.show_icon,
// width: 12.h,
// height: 12.h,
// fit: BoxFit.contain,
// ),
// ),
// ),
// ],
// ),
Consumer<HabibWalletViewModel>(builder: (context, habibWalletVM, child) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.habibWallet.tr(context: context).toText16(isBold: true, letterSpacing: -0.2),
Icon(
habibWalletVM.isWalletAmountHidden ? Icons.visibility_off : Icons.visibility,
color: AppColors.textColor,
size: 24.h,
).onPress(() => habibWalletVM.toggleWalletAmountVisibility()),
],
);
}),
SizedBox(height: 4.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -98,9 +91,9 @@ class HabibWalletCard extends StatelessWidget {
fit: BoxFit.contain,
),
SizedBox(width: 8.h),
NumberFormat.decimalPattern()
.format(habibWalletVM.habibWalletAmount)
.toString()
(habibWalletVM.isWalletAmountHidden
? "****"
: NumberFormat.decimalPattern().format(habibWalletVM.habibWalletAmount).toString())
.toText32(isBold: true, isEnglishOnly: true)
.toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h),
],

@ -67,26 +67,19 @@ class LakumWalletCard extends StatelessWidget {
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// LocaleKeys.habibWallet.tr(context: context).toText16(isBold: true, letterSpacing: -0.2),
LocaleKeys.lakumPoints.tr(context: context).toText16(isBold: true, letterSpacing: -0.2),
// Container(
// height: 40.h,
// width: 40.h,
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
// color: AppColors.textColor,
// borderRadius: 8.h,
// ),
// child: Padding(
// padding: EdgeInsets.all(8.h),
// child: Utils.buildSvgWithAssets(
// icon: AppAssets.show_icon,
// width: 12.h,
// height: 12.h,
// fit: BoxFit.contain,
// ),
// ),
// ),
// ],
// ),
Consumer<HabibWalletViewModel>(builder: (context, habibWalletVM, child) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.lakumPoints.tr(context: context).toText16(isBold: true, letterSpacing: -0.2),
Icon(
habibWalletVM.isLakumPointsHidden ? Icons.visibility_off : Icons.visibility,
color: AppColors.textColor,
size: 24.h,
).onPress(() => habibWalletVM.toggleLakumPointsVisibility()),
],
);
}),
SizedBox(height: 8.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -98,13 +91,15 @@ class LakumWalletCard extends StatelessWidget {
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
NumberFormat.decimalPattern()
.format(habibWalletVM.isLakumAccountInfoLoading
? 123
: habibWalletVM.yahalaAccountNumber == "0"
? 0
: (habibWalletVM.lakumAccountInfo?.pointsBalance ?? 0))
.toString()
(habibWalletVM.isLakumPointsHidden
? "****"
: NumberFormat.decimalPattern()
.format(habibWalletVM.isLakumAccountInfoLoading
? 123
: habibWalletVM.yahalaAccountNumber == "0"
? 0
: (habibWalletVM.lakumAccountInfo?.pointsBalance ?? 0))
.toString())
.toText32(isBold: true, isEnglishOnly: true)
.toShimmer2(isShow: habibWalletVM.isLakumAccountInfoLoading, radius: 12.h, width: 80.h, height: 40.h),
SizedBox(width: 4.h),

@ -128,7 +128,9 @@ class LargeServiceCard extends StatelessWidget {
),
],
),
);
).onPress(() {
handleOnTap();
});
}
void handleOnTap() {
@ -275,7 +277,9 @@ class FadedLargeServiceCard extends StatelessWidget {
),
],
),
);
).onPress(() {
handleOnTap();
});
}
void handleOnTap() {

@ -0,0 +1,97 @@
import 'dart:developer';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.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/occasion_theme/occasion_theme_view_model.dart';
import 'package:provider/provider.dart';
/// Renders the occasion's main header/banner (`header_image`, e.g. the
/// National Day greeting banner) inline at the top of the landing page's
/// scrollable content, drawn at full opacity.
///
/// Fully data-driven: when no occasion is active (the default), this widget
/// renders nothing and takes zero space — the existing layout/UI is
/// completely unaffected.
///
/// See also [OccasionBackgroundWidget], which renders the decorative
/// backdrop image as a full-page background instead of inline content.
class OccasionBannerWidget extends StatelessWidget {
const OccasionBannerWidget({super.key});
@override
Widget build(BuildContext context) {
return Consumer<OccasionThemeViewModel>(
builder: (context, occasionThemeVm, _) {
final headerImageUrl = occasionThemeVm.headerImage;
final hasHeaderImage = occasionThemeVm.isOccasionActive && (headerImageUrl?.isNotEmpty ?? false);
if (!hasHeaderImage) {
return const SizedBox(height: 0);
}
return buildOccasionImage(headerImageUrl!).paddingOnly(bottom: 16.h);
},
);
}
}
/// Renders the occasion's decorative backdrop/watermark
/// (`background_opacity_image`) as a full-page background. Place this as
/// the first child of the landing page's outer [Stack] (behind the
/// scrollable content) so it fills the whole screen and sits behind
/// everything else, drawn at [OccasionThemeViewModel.backgroundImageOpacity].
///
/// Fully data-driven: when no occasion is active (the default), this widget
/// renders nothing — the existing layout/UI is completely unaffected.
class OccasionBackgroundWidget extends StatelessWidget {
const OccasionBackgroundWidget({super.key});
@override
Widget build(BuildContext context) {
return Consumer<OccasionThemeViewModel>(
builder: (context, occasionThemeVm, _) {
final backgroundImageUrl = occasionThemeVm.backgroundOpacityImage;
final hasBackgroundImage = occasionThemeVm.isOccasionActive && (backgroundImageUrl?.isNotEmpty ?? false);
if (!hasBackgroundImage) {
return const SizedBox.shrink();
}
return Positioned.fill(
child: Opacity(
opacity: occasionThemeVm.backgroundImageOpacity.clamp(0.0, 1.0),
child: buildOccasionImage(backgroundImageUrl!, isNeedPadding: true),
),
);
},
);
}
}
/// Remote-config image URLs may point to either an SVG (e.g. the National
/// Day banner) or a raster image (PNG/JPG). [CachedNetworkImage] cannot
/// decode SVG — it silently renders nothing — so SVG URLs are routed to
/// [SvgPicture.network] instead, based on the URL's file extension.
Widget buildOccasionImage(String url, {bool isNeedPadding = false}) {
final isSvg = url.split('?').first.toLowerCase().endsWith('.svg');
if (isSvg) {
return SvgPicture.network(
url,
width: double.infinity,
fit: BoxFit.contain,
placeholderBuilder: (_) => const SizedBox.shrink(),
);
}
return CachedNetworkImage(
imageUrl: url,
width: double.infinity,
fit: BoxFit.contain,
placeholder: (_, __) => const SizedBox.shrink(),
errorWidget: (_, __, error) {
return const SizedBox.shrink();
},
).paddingSymmetrical(isNeedPadding ? 24.w : 0, 0);
}

@ -38,7 +38,7 @@ class SmallServiceCard extends StatelessWidget {
final Color? backgroundColor;
final bool isBold;
SmallServiceCard({
const SmallServiceCard({
super.key,
this.icon = "",
this.serviceName = "",

@ -41,9 +41,13 @@ class WelcomeWidget extends StatelessWidget {
borderRadius: BorderRadius.circular(30),
child: Row(
mainAxisSize: MainAxisSize.min,
spacing: 8.h,
// spacing: 8.h,
children: [
Icon(Icons.menu, color: AppColors.textColor).onPress(() {
SizedBox(
height: 40.h,
width: 20.w,
child: Icon(Icons.menu, color: AppColors.textColor),
).onPress(() {
getIt.get<ProfileSettingsViewModel>().getProfileSettings();
Navigator.of(context).push(
CustomPageRoute(
@ -92,7 +96,7 @@ class WelcomeWidget extends StatelessWidget {
],
),
],
).expanded,
).paddingOnly(left: 8.h, right: 8.h).expanded,
],
),
),

@ -84,7 +84,7 @@ class InsuranceApprovalDetailsPage extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.buildImgWithNetwork(
url: insuranceApprovalResponseModel.doctorImageURL!,
url: insuranceApprovalResponseModel.doctorImageURL,
iconColor: AppColors.blackBgColor,
width: 63.h,
height: 63.h,
@ -102,7 +102,7 @@ class InsuranceApprovalDetailsPage extends StatelessWidget {
spacing: 3.h,
runSpacing: 4.h,
children: [
AppCustomChipWidget(labelText: insuranceApprovalResponseModel.clinicName!),
AppCustomChipWidget(labelText: insuranceApprovalResponseModel.clinicName ?? ""),
AppCustomChipWidget(labelText: "${LocaleKeys.approvalNo.tr(context: context)} ${insuranceApprovalResponseModel.approvalNo}", isEnglishOnly: true),
AppCustomChipWidget(labelText: "${LocaleKeys.unusedCount.tr(context: context)} ${insuranceApprovalResponseModel.unUsedCount}", isEnglishOnly: true),
AppCustomChipWidget(labelText: "${LocaleKeys.companyName.tr(context: context)} ${insuranceApprovalResponseModel.companyName}"),

@ -107,7 +107,7 @@ class InsuranceApprovalCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.buildImgWithNetwork(
url: isLoading ? "https://hmgwebservices.com/Images/MobileImages/OALAY/1439.png" : insuranceApprovalResponseModel.doctorImageURL!,
url: isLoading ? "https://hmgwebservices.com/Images/MobileImages/OALAY/1439.png" : insuranceApprovalResponseModel.doctorImageURL,
iconColor: AppColors.blackBgColor,
width: 63.h,
height: 63.h,

@ -69,8 +69,8 @@ class InsuranceHistory extends StatelessWidget {
CustomButton(
text: insuranceVM.patientInsuranceCardHistoryList[index].statusDescription ?? "",
onPressed: () {},
backgroundColor: AppColors.primaryRedColor.withOpacity(0.1),
borderColor: AppColors.primaryRedColor.withOpacity(0.0),
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
fontSize: 10,
isBold: true,

@ -117,7 +117,7 @@ class PatientInsuranceCard extends StatelessWidget {
}
},
backgroundColor: isCurrentPatientInsuranceExpired(insuranceCardDetailsModel.cardValidTo!)
? AppColors.primaryRedColor.withOpacity(0.1)
? AppColors.secondaryLightRedColor
: insuranceViewModel.isInsuranceActive
? AppColors.successColor.withOpacity(0.1)
: AppColors.warningColorYellow.withOpacity(0.1),
@ -129,7 +129,7 @@ class PatientInsuranceCard extends StatelessWidget {
// iconColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor,
// textColor: isInsuranceExpired ? AppColors.primaryRedColor : AppColors.successColor,
// iconSize: 12,
// backgroundColor: isInsuranceExpired ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1),
// backgroundColor: isInsuranceExpired ? AppColors.primaryRedColorBase.withOpacity(0.1) : AppColors.successColor.withOpacity(0.1),
// labelPadding: EdgeInsetsDirectional.only(start: -4.h, end: 8.h),
// ),
],

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save