diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0e1749d6..fef05b1a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -39,7 +39,6 @@ - @@ -52,7 +51,7 @@ - + diff --git a/assets/images/png/ambulance_section.png b/assets/images/png/ambulance_section.png new file mode 100644 index 00000000..62ca1716 Binary files /dev/null and b/assets/images/png/ambulance_section.png differ diff --git a/assets/images/png/female_bmi.png b/assets/images/png/female_bmi.png new file mode 100644 index 00000000..437f1dc1 Binary files /dev/null and b/assets/images/png/female_bmi.png differ diff --git a/assets/images/png/rrt_section.png b/assets/images/png/rrt_section.png new file mode 100644 index 00000000..b28d5993 Binary files /dev/null and b/assets/images/png/rrt_section.png differ diff --git a/assets/images/svg/bfc.svg b/assets/images/svg/bfc.svg new file mode 100644 index 00000000..a6d2ef3a --- /dev/null +++ b/assets/images/svg/bfc.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/bmi1.svg b/assets/images/svg/bmi1.svg new file mode 100644 index 00000000..c957dc8e --- /dev/null +++ b/assets/images/svg/bmi1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/bmr1.svg b/assets/images/svg/bmr1.svg new file mode 100644 index 00000000..7e403a81 --- /dev/null +++ b/assets/images/svg/bmr1.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/cloudy_weather.svg b/assets/images/svg/cloudy_weather.svg new file mode 100644 index 00000000..bc7d54bf --- /dev/null +++ b/assets/images/svg/cloudy_weather.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/svg/cloudyweather.svg b/assets/images/svg/cloudyweather.svg new file mode 100644 index 00000000..6f8ebb47 --- /dev/null +++ b/assets/images/svg/cloudyweather.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/svg/ddc.svg b/assets/images/svg/ddc.svg new file mode 100644 index 00000000..f671baf8 --- /dev/null +++ b/assets/images/svg/ddc.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/document.svg b/assets/images/svg/document.svg new file mode 100644 index 00000000..5073ead5 --- /dev/null +++ b/assets/images/svg/document.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/evening.svg b/assets/images/svg/evening.svg new file mode 100644 index 00000000..7932d78d --- /dev/null +++ b/assets/images/svg/evening.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/svg/ibwc.svg b/assets/images/svg/ibwc.svg new file mode 100644 index 00000000..acfba187 --- /dev/null +++ b/assets/images/svg/ibwc.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/location_unavailable_filled.svg b/assets/images/svg/location_unavailable_filled.svg new file mode 100644 index 00000000..8bc315c1 --- /dev/null +++ b/assets/images/svg/location_unavailable_filled.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/location_unavailable_filled_background.svg b/assets/images/svg/location_unavailable_filled_background.svg new file mode 100644 index 00000000..cb3fa768 --- /dev/null +++ b/assets/images/svg/location_unavailable_filled_background.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/morning.svg b/assets/images/svg/morning.svg new file mode 100644 index 00000000..0f0d9904 --- /dev/null +++ b/assets/images/svg/morning.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/assets/images/svg/op.svg b/assets/images/svg/op.svg new file mode 100644 index 00000000..ff400de8 --- /dev/null +++ b/assets/images/svg/op.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/svg/report.svg b/assets/images/svg/report.svg new file mode 100644 index 00000000..b5ca54b1 --- /dev/null +++ b/assets/images/svg/report.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/sucess.svg b/assets/images/svg/sucess.svg new file mode 100644 index 00000000..6df93d5e --- /dev/null +++ b/assets/images/svg/sucess.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/whitecalender.svg b/assets/images/svg/whitecalender.svg new file mode 100644 index 00000000..ff400de8 --- /dev/null +++ b/assets/images/svg/whitecalender.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 08adc616..72199078 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -2,18 +2,18 @@ "english": "الإنجليزية", "arabic": "العربية", "login": "تسجيل الدخول", - "noDataAvailable": "لا توجد بيانات متاحة", + "noDataAvailable": "لا توجد طلبات", "noRatingAvailable": "لا يوجد تقييم متاح", "doctorDoesNotHaveRating": "لم يحصل هذا الطبيب على أي تقييمات بعد.", "confirm": "تأكيد", - "loadingText": "جاري التحميل، يرجى الانتظار...", + "loadingText": "يرجى الانتظار…", "kilometerUnit": "كم", "dashboardScreenToolbarTitle": "الرئيسية", "settings": "الإعدادات", "language": "لغة التطبيق", "lanEnglish": "الإنجليزية", "lanArabic": "العربية", - "cancel": "إلغاء", + "cancel": "لا", "done": "تم", "replay2": "إعادة التشغيل", "home": "الرئيسية", @@ -40,7 +40,7 @@ "noResultFound": "لم يتم العثور على نتائج", "noSearchResultFound": "لم يتم العثور على نتائج", "pleaseEnterProductName": "يرجى إدخال اسم المنتج", - "bookNow": "احجز الآن", + "bookNow": "حجز موعد", "docInfo": "معلومات الطبيب", "appoInfo": "معلومات الموعد", "availableAppo": "المواعيد المتاحة", @@ -68,8 +68,8 @@ "appoActions": "إجراءات الموعد", "date": "التاريخ", "time": "الوقت", - "pendingPayment": "معلق للدفع", - "payNow": "ادفع الآن", + "pendingPayment": "إتمام الدفع", + "payNow": "الدفع", "viewQR": "عرض رمز الاستجابة السريعة", "instruction": "تعليمات", "livecare": "لايف كير", @@ -81,8 +81,10 @@ "upcomingNoAction": "لا يتطلب أي إجراء", "upcomingConfirm": "يرجى تأكيد الموعد لتجنب الإلغاء", "bookSuccessConfirmMore2412": "ستكون عملية الدفع عبر الإنترنت متاحة قبل 24 ساعة ��ن الموعد.", - "upcomingPaymentPending": "سيتم تفعيل الدفع عبر الإنترنت قبل 24 ساعة من موعد الموعد", - "upcomingPaymentNow": "ادفع عبر الإنترنت الآن لتجنب الانتظار الطويل", + "upcomingPaymentPending": "سيتم تفعيل الدفع عبر الإنترنت قبل 24 ساعة من وقت الموعد", + "upcomingPaymentNow": "الدفع الإلكتروني لتجنب الانتظار", + "timeUntilAppointment": "الوقت المتبقي حتى الموعد", + "appointmentTimePassed": "انتهى وقت الموعد", "upcomingQR": "استخدم رمز الاستجابة السريعة لتسجيل الوصول في المستشفى", "upcomingVirtual": "هذا موعد افتراضي، يرجى الالتزام بالتعليمات لتجربة سلسة.", "upcomingLivecare": "هذا موعد لايف كير", @@ -91,15 +93,15 @@ "raise": "رفع", "complaint": "شكوى", "add": "إضافة", - "reminder": "تذكير", + "reminder": "حفظ", "location": "المواقع", - "online": "عبر الإنترنت", + "online": "متصل", "payment": "الدفع", "onlinePayment": "عبر الإنترنت", "onlinePaymentSubtitle": "الدفع", - "cancelNocaps": "إلغاء", - "setReminder": "ضبط تذكير", - "nationalIdNumber": "رقم الهوية الوطنية", + "cancelNocaps": "الاحتفاظ بالموعد", + "setReminder": "تذكير", + "nationalIdNumber": "رقم الهوية / الإقام", "medicalFileNumber": "رقم الملف الطبي", "loginregister": "تسجيل الدخول / التسجيل", "poweredBy": "مدعوم من", @@ -112,7 +114,7 @@ "yes": "نعم", "no": "لا", "logintyperadio": "اختر من الخيارات أدناه لتسجيل الدخول إلى ملفك الطبي.", - "registernow": "سجل الآن", + "registernow": "تسجيل", "nationalID": "أدخل رقم الهوية", "nationalId": "الهوية الوطنية", "fileNo": "رقم الملف", @@ -121,10 +123,10 @@ "forgotFileNoTitle": "نسيت الملف الطبي", "enterNationalId": "يرجى إدخال رقم الهاتف المحمول ورقم الهوية", "profileInfo": "يرجى إدخال معلومات الملف الشخصي", - "submit": "إرسال", + "submit": "تحديث", "forgotDesc": "أدخل رقم الهاتف المحمول لاستلام رقم الملف الطبي عبر الرسائل القصيرة", - "dob": "تاريخ الميلاد:", - "hijriDate": "الت��ريخ الهجري", + "dob": "تاريخ الميلاد", + "hijriDate": "التاريخ الهجري", "gregorianDate": "التاريخ الميلادي", "verifyLoginWith": "يرجى اختيار واحدة من الخيارات التالية للتحقق", "registerUser": "تسجيل", @@ -171,12 +173,12 @@ "emailSentError": "خطأ في إرسال البريد الإلكتروني", "close": "إغلاق", "closeIt": "أغلق", - "booked": "محجوز", + "booked": "تم الحجز", "confirmed": "مؤكد", - "arrived": "وصل", + "arrived": " تم الوصول", "payNowBookSuccess": "ادفع الآن عبر تطبيق الحبيب", "payNowBookSuccesstext1": "ادفع الآن باستخدام خدمة الدفع عبر الإنترنت من بوابات الدفع الآمنة", - "payNowBookSuccesstext2": "يمكنك أيضًا الدفع لاحقًا عبر الدفع الإلكتروني أو في المستشفى.", + "payNowBookSuccesstext2": "يمكنك أيضًا الدفع لاحقًا عبر الدفع الإلكت§روني أو في المستشفى.", "payLater": "ادفع لاحقًا", "askDocNotAllowed": "هذه الخدمة ستكون متاحة فقط لزيارة الطبيب قبل 15 يومًا", "moreVerify": "خيارات تحقق إضافية", @@ -196,6 +198,7 @@ "enterEmail": "يرجى إدخال البريد الإلكتروني", "family": "عائلتي", "familyTitle": "ملفات عائلتي", + "familyTitle2": "ملفات العائلة", "myFamily": "عائلتي", "addNewMember": "إضافة فرد جديد للعائلة", "sentRequests": "الطلبات المرسلة", @@ -214,9 +217,9 @@ "heightUnit": "وحدة الطول", "weightUnit": "وحدة الوزن", "unit": "الوحدة", - "request": "طلب", + "request": "الطلبات", "memberName": "اسم العضو", - "switchLogin": "تبديل", + "switchLogin": "عرض", "removeMember": "إزالة", "allowView": "السماح", "rejectView": "رفض", @@ -269,12 +272,13 @@ "userViewRequester": "يريد المستخدم عرض ملفك الطبي", "userView": "يمكن للمستخدم عرض ملفك الطبي", "parking": "موقف السيارات", + "parking2": " المواقف", "alhabiServices": "خدمة HMG", "parkingTitle": "خدمة السيارة، خدمة السيارة، خدمة لحفظ معلومات الموقف، العودة إليها لاحقًا، 1- من خلال النقر على (قراءة الرمز)، حفظ بيانات الموقف. 2- من خلال النقر على زر (عرض موقف سيارتي)، يظهر لك موقع السيارة في خرائط Google. 3- قراءة موضع آخر بالضغط على زر مسح بيانات الموضع.", "parkingDescription": "خدمة المواقف هي لك لمسح موقع السيارة حتى تتمكن من العثور عليها بسهولة عند مغادرتك. انقر على زر (مسح الموقف) لحفظ موقع الموقف، ثم سيظهر زر (عرض موقفي) لإظهار الطريق إلى الموقف. إذا كنت ترغب في إعادة مسح موقف السيارة QR فقط انقر على (مسح بياناتي).", "appointments": "المواعيد", "all2": "الكل", - "checkinOption": "تسجيل الوصول", + "checkinOption": "تسجيل الحضور", "readBarcode": "قراءة الكود", "showMyPark": "عرض الموقف", "clearMyData": "امسح البيانات", @@ -287,7 +291,7 @@ "locationa": "الموقع", "callNow": "اتصل الآن", "ambulancerequest": "طلب إسعاف", - "requestA": "طلب", + "requestA": "الطلبات", "noBookedAppointments": "لا توجد مواعيد محجوزة", "noConfirmedAppointments": "لا توجد مواعيد مؤكدة", "noArrivedAppointments": "لا توجد مواعيد تم تسجيل الوصول إليها", @@ -296,10 +300,10 @@ "radiology": "الأشعة", "radiologySubtitle": "النتيجة", "lab": "المختبر", - "labSubtitle": "النتائج", + "labSubtitle": "نتائج", "medicines": "الأدوية", "medicinesSubtitle": "الوصفات", - "vitalSigns": "علامات حيوية", + "vitalSigns": "العلامات الحيوية", "vitalSignsSubTitle": "التقارير", "vitalSignNormal": "طبيعي", "vitalSignLow": "منخفض", @@ -307,13 +311,16 @@ "vitalSignObese": "بدين", "vitalSignOverweight": "زيادة الوزن", "vitalSignUnderweight": "نقص الوزن", + "vitalSignSkinny": "نحيف جداً", "myMedical": "نشط", "myMedicalSubtitle": "الأدوية", "myDoctor": "أطبائي", "myDoctorSubtitle": "القائمة", + "myDoctorSubtitle2": "قائمة", "eye": "العين", "eyeSubtitle": "القياس", "insurance": "التأمين", + "insurance2": "تأمين", "insuranceSubtitle": "البطاقة", "updateInsurance": "تحديث", "updateInsuranceSubtitle": "التأمين", @@ -323,11 +330,12 @@ "allergiesSubtitle": "القائمة", "myVaccines": "لقاحاتي", "myVaccinesSubtitle": "القائمة", - "medical": "طبي", + "medical": "الطبي", "medicalSubtitle": "التقرير", "monthly": "شهري", "monthlySubtitle": "التقرير", "sick": "مريض", + "sick2": "المرضية", "sickSubtitle": "الإجازات", "myBalance": "رصيدي", "myBalanceSubtitle": "الرصيد", @@ -341,13 +349,13 @@ "askYourSubtitle": "طبيبك", "internet": "الإنترنت", "internetSubtitle": "الاقتران", - "chatbot": "الدرد��ة الآلية", + "chatbot": "الدردشة الآلية", "chatbotSubtitle": "", "timeLine": "الخط الزمني", "labOrders": "طلبات المختبر", "billNo": "رقم الفاتورة:", "prescriptions": "الوصفات", - "history": "التاريخ", + "history": "سجل الطلبات", "orderNo": "رقم الطلب", "orderDetails": "تفاصيل الطلب", "deliveryDriverTrack": "تتبع السائق", @@ -393,7 +401,7 @@ "confirmThePayment": "تأكيد الدفع", "depositorName": "اسم مُقدِّم الإيداع", "mobileNumber": "رقم الجوال", - "phoneNumber": "رقم الهاتف", + "phoneNumber": "رقم الجوال", "country": "البلد", "ok": "حسنا", "averageWaitingTime": "متوسط وقت الانتظار:", @@ -402,7 +410,7 @@ "waterConsumedInMonth": "الماء المستهلك في شهر", "theVerificationCodeExpiresIn": "ينتهي رمز التحقق في", "pleaseEnterTheVerificationCode": "يرجى إدخال رمز التحقق المرسل إلى", - "eyeMeasurements": "قياسات العين", + "eyeMeasurements": "فحوصات النظر", "measurements": "قياسات", "classes": "نظارات", "contactLens": "عدسات لاصقة", @@ -429,7 +437,7 @@ "startDay": "يوم البدء", "endDay": "يوم الانتهاء", "days": "أيام ", - "pleaseVerify": "يرجى التحقق ", + "pleaseVerify": "إضافة", "scheduleTime": "وقت الجدولة", "askDoctor": "اسأل الطبيب", "doctorResponses": "ردود الطبيب", @@ -449,7 +457,7 @@ "infoRadiology": "تتيح لك هذه الخدمة عرض تقارير وصور الأشعة في مجموعة الحبيب الطبية بالإضافة إلى إرسال التقرير عبر البريد الإلكتروني.", "noReviewsAvailable": "لا توجد تقييمات متاحة", "noLocationAvailable": "لا توجد مواقع متاحة", - "orders": "الطلبات", + "orders": "سجل الطلبات", "lakum": "لكم", "lakumMsg": "لا توجد تفاصيل عن النقاط", "lakumPoint": "نقطة", @@ -481,7 +489,7 @@ "shareReview": "شارك التقييم", "review": " التقييمات", "viewMedicalFile": "عرض التفاصيل", - "viewAllServices": "عرض جميع الخدمات", + "viewAllServices": "جميع الخدمات", "medicalFile": "الملف الطبي", "verified": "تم التحقق", "checkup": "فحص", @@ -490,9 +498,11 @@ "refferalTitle": "إحالة إلكترونية", "refferalSubTitle": "الخدمة", "healthCare": "الرعاية الصحية", - "emergency": "حالة طوارئ", + "emergency": "طوارئ", + "emergency2": "الطوارئ", "erservices": "خدمات الطوارئ", "services2": "الخدمات", + "services3": "خدمات", "cantSeeProfile": "لرؤية ملفك الطبي، يرجى تسجيل الدخول أو التسجيل الآن", "loginRegisterNow": "تسجيل الدخول أو التسجيل الآن", "hmgPharmacy": "صيدلية الحبيب", @@ -597,7 +607,7 @@ "selectGender": "اختر الجنس", "iAmA": "أنا ...", "selectAge": "اختر عمرك", - "select": "اختر", + "select": "تأكيد الموعد", "iAm": "أنا", "yearsOld": "سنوات", "dragPoint": "اسحب النقطة لتغيير عمرك", @@ -670,8 +680,8 @@ "noOffersAvailable": "لا توجد عروض متاحة!", "accessibility": "وضع الوصول", "orderStatus": "حالة الطلب", - "findUs": "اعثر علينا", - "liveChat": "دردشة مباشرة", + "findUs": "فروعنا", + "liveChat": "محادثة مباشرة", "service": "خدمة", "hmgServiceLabel": "خدمة مجموعة الحبيب الطبية", "healthWeatherIndicators": "مؤشرات الطقس الصحية", @@ -685,8 +695,8 @@ "duration": "المدة", "cm": "سم", "ft": "قدم", - "kg": "كجم", - "lb": "رطل", + "kg": "كغ", + "lb": "باوند", "birthDate": "تاريخ الميلاد", "dateOfBirth": "تاريخ الميلاد", "mass": "الكتلة", @@ -701,12 +711,12 @@ "bikini": "بيكيني", "totalMinutes": "إجمالي الدقائق", "feedback": "رأيك يهمنا", - "likeToHear": "نود سماع ملاحظاتك، ومخاوفك بشأن خدمات الرعاية الصحية وتجربة الخدمات الإلكترونية. يرجى استخدام النموذج أدناه", + "likeToHear": "يسعدنا سماع رأيك. شاركنا تجربتك أو أي ملاحظات حول خدماتنا الطبية أو الالكترونية عبر النموذج أدناه", "emptySubject": "يرجى إدخال الموضوع", "emptyMessage": "يرجى إدخال الرسالة", - "selectAttachment": "اختر المرفق", - "complainAppo": "شكوى بخصوص الموعد", - "complainAppoAlt": "شكوى بدون موعد", + "selectAttachment": "اختر الملف", + "complainAppo": "شكوى مع وجود موعد", + "complainAppoAlt": "شكوى بدون وجود موعد", "messageType": "نوع الرسالة", "feedbackType": "نوع الرسالة", "compliment": "تقدير", @@ -759,13 +769,14 @@ "infoTodo": "تم تصميم هذه الخدمة لتمكينك من الحصول على رابط سريع لقائمة المهام التي تحتاج القيام بها", "familyInfo": "من خلال هذه الخدمة، ستتمكن من ربط ملفات عائلتك الطبية بملفك الطبي حتى تتمكن من إدارة سجلاتهم عن طريق تسجيل الدخول إلى ملفك الطبي.", "rrtdDetails": "تقدم فريق الاستجابة السريعة خدمة طبية شاملة لجميع أنواع الحالات العاجلة والمستقرة من جميع الأعمار، بما في ذلك البالغين والمرضى الأطفال، وتغطي الخدمة فريق طبي مدرب تدريبًا عاليًا ومجهز تجهيزًا كاملاً قادرًا على تقديم أفضل رعاية طبية في منزل المريض.", - "homeHealthCareText": "تقدم هذه الخدمة مجموعة من خدمات الرعاية الصحية المنزلية، والمتابعة المستمرة والشاملة في أماكن إقامتهم لأولئك الذين لا يمكنهم الوصول إلى المرافق الصحية، مثل (تحليلات المختبر - الأشعة - التطعيمات - العلاج الطبيعي)، إلخ.", + "homeHealthCareText": "أعلى معايير الرعاية و الخدمات الطبية في راحة منزلك", "onlineCheckInAgreement": "تسجيل الوصول عبر الإنترنت مخصص للحالات التي لا تهدد الحياة. اتصل بالهلال الأحمر (997) أو اذهب إلى أقرب قسم طوارئ إذا كان هناك: \n\nعلامات السكتة الدماغية أو النوبة القلبية \nhالة من النوبات أو الإغماء \nهناك إصابة تهدد الطرف أو الحياة \nصورة لإصابات خطيرة", "infoEreferral": "تتيح لك هذه الخدمة تقديم طلب إحالة من أي مقدم رعاية صحية سواء داخل أو خارج المملكة العربية السعودية إلى أي من مستشفيات HMG، عن طريق ملء بعض بيانات المريض وإرفاق التقارير الطبية، علاوة على ذلك يمكنك تتبع حالة الطلب (قيد المعالجة، مقبول أو مرفوض)", "erConsultation": "تتيح لك هذه الخدمة إجراء استشارة افتراضية عبر الإنترنت عبر مكالمة فيديو مباشرة مع الطبيب من أي مكان وفي أي وقت.", "myInvoice": "القائمة", "invoiceList": "فواتيري", - "allInvoices": "كل الفواتير", + "invoiceList2": "فواتيري", + "allInvoices": "الكل", "hospitals": "المستشفيات", "clinics": "العيادات", "doctors": "الأطباء", @@ -790,47 +801,49 @@ "continuePlan": "متابعة خطة العلاج؟", "aboutApp": "حول التطبيق", "dontHaveAccount": "ليس لديك حساب؟", - "loginOrRegister": "تسجيل الدخول أو التسجيل", + "loginOrRegister": "تسجيل الدخول / إنشاء حساب", "myFiles": "ملفاتي", "resultsPending": "النتائج معلقة", "resultsAvailable": "النتائج متاحة", - "viewReport": "عرض التقرير", + "viewReport": "التفاصيل", "checkAvailability": "التحقق من التوفر", "readInstructions": "قراءة التعليمات", "searchLabReport": "ابحث عن تقرير المختبر", - "prescriptionDeliveryError": "هذه العيادة لا تدعم إعادة التعبئة والتسليم.", - "receiveOtpToast": "أين تود تلقي رمز التحقق OTP؟", - "enterPhoneNumber": "أدخل رقم الهاتف", + "prescriptionDeliveryError": "خدمة الصرف والتوصيل غير متاحة", + "receiveOtpToast": "رمز التحقق", + "enterPhoneNumber": "إدخال رقم الجوال", "enterEmailDesc": "أدخل عنوان بريدك الإلكتروني لإكمال عملية إنشاء ملف طبي", - "enterPhoneDesc": "أدخل رقم هاتفك لتلقي رمز التحقق ", - "pleaseChooseOption": "الرجاء اختيار من الخيارات أدناه لتلقي رمز التحقق OTP", - "prepareToElevate": "هل أنت مستعد لتحسين صحتك ورفاهيتك؟", - "iAcceptTermsConditions": "أوافق على الشروط والأحكام", - "alreadyHaveAccount": "هل لديك حساب بالفعل؟", - "loginNow": "تسجيل الدخول الآن", - "notice": "إشعار", + "enterPhoneDesc": "أدخل رقم جوالك لاستلام رمز التحقق ", + "pleaseChooseOption": "اين ترغب في استلام رمز التحقق؟", + "prepareToElevate": "رعايتك الصحية تبدأ من هنا", + "iAcceptTermsConditions": "الموافقة على الشروط والأحكام", + "alreadyHaveAccount": "لديك حساب؟", + "loginNow": "تسجيل الدخول", + "notice": "تنبيه", "oR": "أو", - "sendOTPWHATSAPP": "أرسل لي OTP عبر واتساب", - "sendOTPSMS": "أرسل لي OTP عبر الرسائل القصيرة", + "sendOTPWHATSAPP": "واتساب", + "sendOTPSMS": "رسالة نصية", "fullName": "الاسم الكامل", "married": "متزوج", "uae": "الإمارات العربية المتحدة", "malE": "ذكر", "loginBy": "تسجيل الدخول بواسطة", - "loginByOTP": "تسجيل الدخول بواسطة OTP", + "loginByOTP": "تسجيل الدخول بواسطة رمزالتحقق", "guest": "زائر", "switchAccount": "تبديل الحساب", "lastLoginBy": "آخر تسجيل دخول بواسطة", - "allSet": "جاهز! الآن يمكنك تسجيل الدخول باستخدام Face ID أو البصمة", - "enableQuickLogin": "تمكين تسجيل الدخول السريع", + "allSet": "الآن يمكنك تسجيل الدخول باستخدام بصمة الوجه أو البصمة", + "quickLogin": "الدخول السريع", + "enableQuickLogin": "تسجيل الدخول السريع", + "enableQuickLoginBtn": " تفعيل الدخول السريع", "enableMsg": "تمكين تسجيل الدخول السريع سيسمح بالتحقق من خلال Face ID / Biometric الخاص بجهازك الحالي", "notNow": "ليس الآن", "pendingActivation": "في انتظار التنشيط", "awaitingApproval": "انتظر القبول", - "news": "أخبار", + "news": "الأخبار", "ready": "جاهز", "enterValidNationalId": "رقم الهوية أو رقم الملف غير صحيح", - "enterValidPhoneNumber": "الرجاء إدخال رقم هاتف صالح", + "enterValidPhoneNumber": "الرجاء ادخال رقم الهاتف", "cannotEnterSaudiOrUAENumber": "لا يمكنك إدخال أرقام هواتف السعودية (00966) أو الإمارات (00971) عند اختيار دولة 'أخرى'", "medicalCentersWithCount": "{count} مراكز طبية", "medicalCenters": "مراكز طبية", @@ -841,15 +854,15 @@ "selectHospitalSubTitle": "يرجى اختيار المستشفى للموعد", "iAcceptThe": "أوافق على", "personalDetailsVerification": "التحقق من التفاصيل الشخصية", - "otpVerification": "التحقق من OTP", - "weHaveSendOTP": "لقد أرسلنا OTP إلى", + "otpVerification": "رمز التحقق", + "weHaveSendOTP": "تم ارسال رمز التحقق الى", "via": "عبر", "forRegistrationVerification": "للتحقق من التسجيل", - "didntReceiveIt": "لم تستلمه؟", + "didntReceiveIt": "لم يصلك الرمز؟", "resendOTP": "إعادة إرسال", - "resendIn": "إعادة الإرسال في", - "pleaseEnterAnationalID": "يرجى إدخال رقم الهوية الوطنية", - "pleaseEnterAFileNumber": "يرجى إدخال رقم الملف", + "resendIn": "إعادة الإرسال خلال", + "pleaseEnterAnationalID": "تسجيل الدخول برقم الهوية او الاقامة", + "pleaseEnterAFileNumber": "يرجى إدخال / رقم الملف", "pleaseEnterAValidEmail": "يرجى إدخال بريد إلكتروني صالح", "pleaseEnterFullName": "يرجى إدخال الاسم الكامل", "pleaseAcceptTermsConditions": "يرجى قبول الشروط والأحكام", @@ -871,27 +884,28 @@ "onboardingBody1": "ببضع نقرات فقط يمكنك استشارة الطبيب الذي تختاره.", "onboardingHeading2": "الوصول إلى السجل الطبي بين يديك", "onboardingBody2": "تتبع تاريخك الطبي بما في ذلك الفحوصات المخبرية، الوصفات الطبية، التأمين، وغيرها.", - "hmgHospitals": "مستشفيات الحبيب", - "hmcMedicalClinic": "مراكز مجموعة الحبيب الطبية", - "applyFilter": "تطبيق الفلتر", - "facilityAndLocation": "المرفق والموقع", - "regionAndLocation": "المنطقة والمواقع", - "clearAllFilters": "مسح جميع الفلاتر", - "filters": "فلاتر", + "hmgHospitals": "المستشفيات", + "hmcMedicalClinic": "المراكز الطبية", + "applyFilter": "تطبيق", + "facilityAndLocation": "المنشآت", + "regionAndLocation": "المنطقة", + "clearAllFilters": "اعادة تعيين", + "filters": "تحديد البحث", "searchClinic": "بحث عن عيادة", - "normal": "عادي", + "normal": "طبيعي", "attention": "انتباه", - "monitor": "مراقبة", + "monitor": "يتطلب متابعة", "noSpecialResult": "لا توجد نتائج خاصة", "setTheDateRange": "تعيين النطاق الزمني", - "historyFlowchart": "مخطط تدفق التاريخ", + "selectDate": "اختر التاريخ", + "historyFlowchart": "السجل التاريخي", "to": "إلى", "startDate": "تاريخ البدء", "endDate": "تاريخ الانتهاء", - "walkin": "زيارة بدون موعد", + "walkin": "موعد", "laserClinic": "عيادة الليزر", "continueString": "يكمل", - "covid_info": "تجري مستشفيات د. سليمان الحبيب فحص فيروس كورونا المستجد وتصدر شهادات السفر على مدار الساعة، طوال أيام الأسبوع، وبسرعة ودقة عالية. يمكن للراغبين في الاستفادة من هذه الخدمة زيارة أحد فروع مستشفيات د. سليمان الحبيب وإجراء فحص كورونا خلال بضع دقائق والحصول على النتائج خلال عدة ساعات خدمة فحص فيروس كورونا Covid 19 بتقنية PCR للكشف عن الفيروس وفقاً لأعلى المعايير العالمية وبأحدث أجهزة RT-PCR عالية الدقة (GeneXpert الأمريكي وغيره)، وهي طرق معتمدة من قبل هيئة الغذاء والدواء وكذلك من قبل المركز السعودي للوقاية من الأمراض المُعدية", + "covid_info": "تجري مستشفيات د. سليمان الحبيب فحص فيروس كورونا المستجد وتصدر شهادات السفر على مدار الساعة، طوال أيام الأسبوع، وبسرعة ودقة عالية. يمكن للراغبين في الاستفادة من هذه الخدمة زيارة أحد فروع مستشفيات د. سليمان الحبيب وإجراء فحص كورونا خلال بضع دقائق والحصول على النتائج خلال عدة ساعات خدمة فحص فيروس كورونا Covid 19 بتقنية PCR للكشف عن الفيروس وفقاً لأعلى المعايير العالمية وبأحدث أجهزة RT-PCR عالية الدقة (GeneXpert الأمريكي وغيره)، وهي طرق معتمدة من قبل هيئة الغذاء والدواء وكذلك من قبل المركز السعودي للوقاية من الأمراض المُعدية", "appointmentDetails": "تفاصيل الموعد", "checkingDoctorAvailability": "جاري التحقق من توفر الطبيب...", "cancellingAppointmentPleaseWait": "جاري إلغاء الموعد، يرجى الانتظار...", @@ -899,8 +913,8 @@ "notConfirmed": "غير مؤكد", "appointmentStatus": "حالة الموعد", "doctorWillCallYou": "سيتصل بك الطبيب عندما يقترب موعدك.", - "getDirections": "الحصول على الاتجاهات", - "notifyMeBeforeAppointment": "أبلغني قبل الموعد", + "getDirections": "الاتجاهات", + "notifyMeBeforeAppointment": "تنبيهي قبل الموعد", "fetchingLabResults": "جاري جلب نتائج المختبر...", "fetchingRadiologyResults": "جاري جلب نتائج الأشعة...", "fetchingAppointmentPrescriptions": "جاري جلب وصفات الموعد...", @@ -908,21 +922,21 @@ "amountBeforeTax": "المبلغ قبل الضريبة", "rebookAppointment": "إعادة حجز الموعد", "fetchingDoctorSchedulePleaseWait": "جاري جلب جدول الطبيب، يرجى الانتظار...", - "pickADate": "اختر تاريخاً", + "pickADate": "اختيار الوقت والتاريخ", "confirmingAppointmentPleaseWait": "جاري تأكيد الموعد، يرجى الانتظار...", "appointmentConfirmedSuccessfully": "تم تأكيد الموعد بنجاح", "appointmentPayment": "دفع الموعد", "checkingPaymentStatusPleaseWait": "جاري التحقق من حالة الدفع، يرجى الانتظار...", "paymentFailedPleaseTryAgain": "فشل الدفع! يرجى المحاولة مرة أخرى.", "appointmentCheckIn": "تسجيل حضور الموعد", - "insuranceExpiredOrInactive": "التأمين منتهي الصلاحية أو غير نشط", - "totalAmountToPay": "المبلغ الإجمالي المستحق", - "vat15": "ضريبة القيمة المضافة 15%", + "insuranceExpiredOrInactive": "التأمين غير ساري (منتهي او غير مفعل)", + "totalAmountToPay": "المبلغ الإجمالي", + "vat15": "ضريبة القيمة المضافة (15%)", "general": "عام", "liveCare": "لايف كير", - "recentVisits": "الزيارات الأخيرة", + "recentVisits": "قائمة الزيارات", "favouriteDoctors": "الأطباء المفضلون", - "searchByClinic": "البحث حسب العيادة", + "searchByClinic": "العيادة", "tapToSelectClinic": "انقر لاختيار العيادة", "searchByDoctor": "البحث حسب الطبيب", "tapToSelect": "انقر للاختيار", @@ -931,8 +945,8 @@ "immediateConsultation": "استشارة فورية", "scheduledConsultation": "استشارة مجدولة", "pharmaLiveCare": "لايف كير الصيدلية", - "notSureHelpMeChooseClinic": "غير متأكد؟ ساعدني في اختيار عيادة!", - "mentionYourSymptomsAndFindDoctors": "اذكر أعراضك واعثر على قائمة الأطباء وفقاً لذلك", + "notSureHelpMeChooseClinic": "هل تريد مساعدة في اختيار العياة؟", + "mentionYourSymptomsAndFindDoctors": "أدخل حالتك الصحية للحصول على قائمة الأطباء", "immediateService": "خدمة فورية", "noNeedToWaitGetMedicalConsultation": "لا حاجة للانتظار، ستحصل على استشارة طبية فورية عبر مكالمة فيديو", "noVisitRequired": "لا حاجة للزيارة", @@ -941,7 +955,7 @@ "freeMedicineDelivery": "توصيل مجاني للأدوية", "offersFreeMedicineDelivery": "يوفر توصيل مجاني للأدوية لموعد لايف كير", "dentalChiefComplaints": "الشكاوى الرئيسة للأسنان", - "viewAvailableAppointments": "عرض المواعيد المتاحة", + "viewAvailableAppointments": "المواعيد المتاحة", "doctorProfile": " ملف الطبيب", "waitingAppointment": "موعد الانتظار", "hospitalInformation": "معلومات المستشفى", @@ -958,22 +972,22 @@ "viewNearestAppos": "عرض أقرب المواعيد المتاحة", "noDoctorFound": "لم يتم العثور على طبيب مطابق للمعايير المحددة...", "yesPleasImInAHurry": "نعم من فضلك، أنا في عجلة من أمري", - "fetchingFeesPleaseWait": "جاري جلب الرسوم، يرجى الانتظار...", + "fetchingFeesPleaseWait": "يرجى الانتظار...", "noThanksPhysicalVisit": "لا، شكراً. أفضل الزيارة الشخصية", "offline": "غير متصل", "videoCall": "مكالمة فيديو", - "liveVideoCallWithHMGDoctors": "مكالمة فيديو مباشرة مع أطباء مجموعة الحبيب الطبية", - "liveAudioCallWithHMGDoctors": "مكالمة صوتية مباشرة مع أطباء مجموعة الحبيب الطبية", + "liveVideoCallWithHMGDoctors": "مكالمة فيديو مباشرة مع مختص", + "liveAudioCallWithHMGDoctors": "مكالمة صوتية مباشرة مع مختص", "audioCall": "مكالمة صوتية", "phoneCall": "مكالمة هاتفية", - "livePhoneCallWithHMGDoctors": "مكالمة هاتفية مباشرة مع أطباء مجموعة الحبيب الطبية", - "reviewLiveCareRequest": "مراجعة طلب لايف كير", - "selectedLiveCareType": "نوع لايف كير المحدد", - "selectLiveCareCallType": "اختر نوع مكالمة لايف كير", + "livePhoneCallWithHMGDoctors": "مكالمة هاتفية مباشرة مع مختص", + "reviewLiveCareRequest": "تفاصيل الطلب", + "selectedLiveCareType": "نوع الطلب", + "selectLiveCareCallType": "نوع مكالمة لايف كير", "confirmingLiveCareRequest": "جاري تأكيد طلب لايف كير، يرجى الانتظار...", "unknownErrorOccurred": "حدث خطأ غير معروف...", "liveCarePermissionsMessage": "يتطلب لايف كير أذونات الكاميرا والميكروفون والموقع والإشعارات لتمكين الاستشارة الافتراضية بين المريض والطبيب، يرجى السماح بهذه الأذونات للمتابعة.", - "liveCarePayment": "دفع لايف كير", + "liveCarePayment": "الدفع", "mada": "مدى", "visaOrMastercard": "فيزا أو ماستركارد", "tamara": "تمارا", @@ -995,8 +1009,8 @@ "processingCheckIn": "جاري معالجة تسجيل الحضور...", "bookingWaitingAppointment": "جاري حجز موعد الانتظار، يرجى الانتظار...", "enterValidIDorIqama": "يرجى إدخال رقم هوية وطنية أو رقم ملف صالح", - "selectAppointment": "حدد الموعد", - "rebookSameDoctor": "أعد الحجز مع نفس الطبيب", + "selectAppointment": "اختر الموعد", + "rebookSameDoctor": "حجز موعد", "queueing": "قائمة الانتظار", "inQueue": "في قائمة الانتظار", "yourTurn": "دورك", @@ -1017,7 +1031,7 @@ "upcoming": "القادمة", "completed": "المكتملة", "noAppointmentsYet": "ليس لديك أي مواعيد بعد.", - "viewProfile": "عرض الملف الشخصي", + "viewProfile": "الملف الشخصي", "choosePreferredHospitalForService": "اختر المستشفى المفضل لديك للخدمة", "noHospitalsFound": "لم يتم العثور على مستشفيات", "cancelOrderConfirmation": "هل أنت متأكد أنك تريد إلغاء هذا الطلب؟", @@ -1035,9 +1049,9 @@ "noServicesAvailable": "لا توجد خدمات متاحة", "selectAService": "اختر خدمة", "comprehensiveCheckup": "الفحص الشامل", - "viewNearestHMGLocations": "عرض أقرب مواقع مجموعة الحبيب الطبية", + "viewNearestHMGLocations": "عرض أقرب فرع", "provideFeedbackOnServices": "قدم ملاحظاتك على خدماتنا", - "liveChatWithHMG": "خيار الدردشة المباشرة مع مجموعة الحبيب الطبية", + "liveChatWithHMG": "حدث مباشرة مع الموظف", "send": "إرسال", "status": "الحالة", "sendingFeedback": "جاري إرسال الملاحظات...", @@ -1085,11 +1099,11 @@ "hereIsYourReferralNumber": "هذا هو رقم الإحالة الخاص بك #: ", "searchEReferral": "البحث عن إحالة إلكترونية", "enterRequiredInfoToSearch": "يرجى إدخال المعلومات المطلوبة للبحث عن إحالة إلكترونية", - "selectPickupDirection": "اختر اتجاه الاستلام", + "selectPickupDirection": "اختر الاتجاه", "selectDirection": "اختر الاتجاه", "toHospital": "إلى المستشفى", "fromHospital": "من المستشفى", - "selectWay": "اختر الطريقة", + "selectWay": "الاتجاهات", "oneWay": "اتجاه واحد", "twoWay": "اتجاهين", "selectPickupDetails": "اختر تفاصيل الاستلام", @@ -1097,16 +1111,16 @@ "selectDetails": "اختر التفاصيل", "work": "العمل", "pick": "استلام", - "insideTheHome": "داخل المنزل", + "insideTheHome": "من المنزل", "haveAnyAppointment": "هل لديك أي موعد", - "amountPaidAtHospital": "سيتم دفع المبلغ في المستشفى", - "submitRequest": "إرسال الطلب", - "enterPickupLocationManually": "أدخل موقع الاستلام يدوياً", + "amountPaidAtHospital": "الدفع عند الوصول للمستشفى", + "submitRequest": "تأكيد الطلب", + "enterPickupLocationManually": "تحديد موقع الاستلام", "enterPickupLocation": "أدخل موقع الاستلام", - "trackingDetails": "تفاصيل التتبع", + "trackingDetails": "استدعاء سيارة إسعاف", "cancelRequest": "إلغاء الطلب", "shareLocationWhatsapp": "مشاركة موقعك المباشر على واتساب", - "pleaseWaitForCall": "يرجى انتظار المكالمة", + "pleaseWaitForCall": "سنتصل بك قريبًا لتأكيد الطلب", "toHospitalLower": "إلى المستشفى", "contact": "اتصال", "failed": "فشل", @@ -1115,7 +1129,7 @@ "onTheWayToHospital": " في الطريق إلى المستشفى", "arrivedAtHospital": "وصل إلى المستشفى", "orderCancel": "إلغاء الطلب", - "emergencyCheckIn": "تسجيل الطوارئ الإلكتروني", + "emergencyCheckIn": "تسجيل الوصول في الطواريء", "erOnlineCheckInDescription": "تتيح هذه الخدمة للمرضى تسجيل موعد الطوارئ قبل الوصول.", "erOnlineCheckInSuccess": "تم تسجيل وصول الطوارئ بنجاح. الرجاء التوجه إلى منطقة الانتظار.", "erOnlineCheckInError": "حدث خطأ غير متوقع أثناء عملية التسجيل. يرجى التواصل مع الدعم.", @@ -1131,35 +1145,42 @@ "allFacilities": "جميع المرافق", "selectLocation": "اختر الموقع", "pleaseSelectTheLocation": "يرجى اختيار الموقع", - "viewLocationGoogleMaps": "عرض الموقع على خرائط جوجل", + "viewLocationGoogleMaps": "الاتجاهات", "callAmbulance": "استدعاء سيارة إسعاف", "requestAmbulanceInEmergency": "طلب سيارة إسعاف في حالة الطوارئ من المنزل أو المستشفى", - "confirmation": "تأكيد", - "areYouSureYouWantToCallAmbulance": "هل أنت متأكد أنك تريد استدعاء سيارة إسعاف؟", - "getDetailsOfNearestBranch": "احصل على تفاصيل أقرب فرع بما في ذلك الاتجاهات", - "areYouSureYouWantToCallRRT": "هل أنت متأكد أنك تريد استدعاء فريق الاستجابة السريعة (RRT)؟", + "confirmation": "استمرار", + "areYouSureYouWantToCallAmbulance": "هل تود تأكيد طلب سيارة الإسعاف؟", + "getDetailsOfNearestBranch": "الحصول على تفاصيل أقرب مركز طوارئ والاتجاهات إليه", + "areYouSureYouWantToCallRRT": "هل تريد استدعاء فريق الاستجابة السريعة؟", "priorERCheckInToSkipLine": "تسجيل الوصول المسبق في الطوارئ لتجاوز الطابور والدفع في الاستقبال.", - "areYouSureYouWantToMakeERCheckIn": "هل أنت متأكد أنك تريد إجراء تسجيل وصول الطوارئ؟", - "checkingYourERAppointmentStatus": "جاري التحقق من حالة موعد الطوارئ الخاص بك...", + "areYouSureYouWantToMakeERCheckIn": "هل تريد إجراء تسجيل وصول الطوارئ؟", + "checkingYourERAppointmentStatus": "يرجى الانتظار…", "transportOptions": "خيارات النقل", - "selectHospitalForAdvancePayment": "يرجى اختيار المستشفى الذي ترغب في دفع مبلغ مقدم له.", - "recharge": "إعادة الشحن", + "selectHospitalForAdvancePayment": "يرجى تحديد المستشفى أولاً.", + "recharge": "شحن المحفظة", "activityLevel": "مستوى النشاط", "selectActivityLevel": "حدد مستوى النشاط", + "almostInactive": "غير نشط تقريبًا (بدون تمارين)", + "lightlyActive": "نشاط خفيف", + "lightlyActive13Days": "نشاط خفيف (١-٣) أيام في الأسبوع", + "superActive": "نشاط مكثف (تمارين شاقة جدًا)", "caloriesPerDay": "السعرات الحرارية في اليوم الواحد", "dietType": "نوع النظام الغذائي", "selectDietType": "حدد نوع النظام الغذائي", "bodyFrameSize": "حجم إطار الجسم", "selectBodyFrameSize": "حدد حجم إطار الجسم", + "bodyFrameSmall": "صغير (الأصابع متداخلة)", + "bodyFrameMedium": "متوسط (الأصابع متلامسة)", + "bodyFrameLarge": "كبير (الأصابع لا تتلامس)", "averageCycleLength": "متوسط طول الدورة الشهرية (عادةً 28 يومًا)", "averageLutealPhase": "متوسط طول المرحلة الأصفرية (عادةً 14 يومًا)", - "convert": "يتحول", + "convert": "تحويل الوحدات", "calculate": "احسب", - "healthCalculators": "حاسبات الصحة", - "healthConverters": "محولات الصحة", + "healthCalculators": "الحاسبات الصحية", + "healthConverters": "تحويل الوحدات", "generalHealth": "الصحة العامة", "relatedToBMICalories": "متعلق بمؤشر كتلة الجسم والسعرات الحرارية ودهون الجسم وما إلى ذلك للبقاء على اطلاع دائم بصحتك.", - "selectCalculator": "اختر الآلة الحاسبة", + "selectCalculator": "اختر الحاسبة", "womensHealth": "صحة المرأة", "relatedToPeriodsOvulation": "متعلق بالدورة الشهرية والإباضة والحمل ومواضيع أخرى.", "bloodSugar": "سكر الدم", @@ -1168,14 +1189,22 @@ "monitorCholesterolLevels": "راقب مستويات الكوليسترول، وقيّم مخاطر صحة القلب، واتخذ خطوات استباقية للرفاهية.", "triglyceridesFatBlood": "الدهون الثلاثية في الدم", "understandTriglyceridesImpact": "افهم تأثير الدهون الثلاثية على صحة القلب مع رؤى مخصصة وتوصيات الخبراء.", - "bmiCalculator": "حاسبة\nمؤشر كتلة الجسم", - "caloriesCalculator": "حاسبة\nالسعرات الحرارية", - "bmrCalculator": "حاسبة\nمعدل الأيض الأساسي", - "idealBodyWeight": "الوزن المثالي\nللجسم", - "bodyFatCalculator": "حاسبة\nدهون الجسم", + "bmiCalculator": "مؤشر\nكتلة الجسم", + "caloriesCalculator": "السعرات \nالحرارية", + "bmrCalculator": "معدل \nالايض", + "idealBodyWeight": "الوزن\n المثالي", + "bodyFatCalculator": "دهون\n الجسم", "carbsProteinFat": "الكربوهيدرات\nالبروتين والدهون", "ovulationPeriod": "فترة\nالإباضة", "deliveryDueDate": "تاريخ الولادة\nالمتوقع", + "bmiCalculatorDesc": "احسب مؤشر كتلة جسمك لفهم نطاق وزنك الصحي.", + "caloriesCalculatorDesc": "اكتشف عدد السعرات الحرارية التي تحتاجها يومياً للحفاظ على الوزن أو فقدانه.", + "bmrCalculatorDesc": "حدد معدل الأيض الأساسي ومتطلباتك اليومية من الطاقة.", + "idealBodyWeightDesc": "اكتشف وزنك المثالي بناءً على الطول والعمر وبنية الجسم.", + "bodyFatCalculatorDesc": "قدّر نسبة دهون جسمك لتتبع أهداف اللياقة والصحة.", + "carbsProteinFatDesc": "وازن بين العناصر الغذائية مع النسب الصحيحة من الكربوهيدرات والبروتين والدهون.", + "ovulationPeriodDesc": "تتبع فترة خصوبتك وخطط للحمل بدقة.", + "deliveryDueDateDesc": "احسب تاريخ ولادتك المتوقع وراقب مراحل الحمل.", "low": "منخفض", "preDiabetic": "ما قبل السكري", "high": "مرتفع", @@ -1209,30 +1238,30 @@ "bookAppointment": "حجز\nموعد", "completeCheckup": "الفحص الشامل", "indoorNavigation": "الملاحة الداخلية", - "eReferralServices": "خدمات الإحالة الإلكترونية", + "eReferralServices": "الاحالة", "bloodDonation": "التبرع بالدم", "dailyWaterMonitor": "مراقب الماء اليومي", "fetchingYourWaterIntakeDetails": "جاري جلب تفاصيل استهلاك الماء الخاص بك.", "healthCalculatorsServices": "حاسبات\nالصحة", "healthConvertersServices": "محولات\nالصحة", "smartWatchesServices": "الساعات\nالذكية", - "exploreServices": "استكشف الخدمات", - "medicalAndCareServices": "الخدمات الطبية والرعاية", + "exploreServices": "قائمة الخدمات", + "medicalAndCareServices": "خدمات الرعاية الطبية", "hmgServices": "خدمات مجموعة الحبيب الطبية", "personalServices": "الخدمات الشخصية", "habibWallet": "محفظة الحبيب", + "habibWallet2": "المحفظة", "loginToViewWalletBalance": "سجل الدخول لعرض رصيد محفظتك", - "recharge": "إعادة الشحن", "loginToViewMedicalFile": "سجل الدخول لعرض ملفك الطبي", "addMember": "إضافة عضو", "addFamilyMember": "إضافة فرد من العائلة", - "pleaseFillBelowFieldToAddNewFamilyMember": "يرجى ملء الحقل أدناه لإضافة فرد جديد من العائلة إلى ملفك الشخصي", - "healthTools": "أدوات الصحة", + "pleaseFillBelowFieldToAddNewFamilyMember": "أدخل البيانات أدناه لإضافة فرد جديد إلى عائلتك", + "healthTools": "الادوات الصحية", "supportServices": "خدمات الدعم", "virtualTour": "جولة افتراضية", "carParking": "موقف السيارات", "latestNews": "آخر الأخبار", - "hmgContact": "اتصل بمجموعة الحبيب الطبية", + "hmgContact": "تواصل معنا", "durationCannotExceed90": "لا يجوز أن تتجاوز المدة 90 دقيقة", "unexpectedError": "حدث خطأ غير متوقع", "gettingAmbulanceTransportOption": "جاري الحصول على خيارات نقل الإسعاف", @@ -1251,10 +1280,10 @@ "invalidWeight": "الوزن غير صالح", "timeToDrinkWater": "حان وقت شرب الماء! 💧", "stayHydratedDrinkWater": "ابق رطبًا! اشرب {amount} مل من الماء.", - "visitPharmacyOnline": "زيارة الصيدلية على الانترنت", + "visitPharmacyOnline": "الصيدلية الالكترونية", "howAreYouFeelingToday": "كيف حالك اليوم؟", - "checkYourSymptomsWithScale": "تحقق من أعراضك باستخدام ذا المقياس", - "checkYourSymptoms": "تحقق من أعراضك", + "checkYourSymptomsWithScale": "قيّم حالتك الصحية واحصل على التوجيه الطبي المناسب", + "checkYourSymptoms": "مدقق الأعراض", "noUpcomingAppointmentPleaseBook": "ليس لديك أي مواعيد قادمة. يرجى حجز موعد", "youHaveEROnlineCheckInRequest": "لديك طلب تسجيل وصول عبر الإنترنت للطوارئ", "quickLinks": "روابط سريعة", @@ -1266,8 +1295,8 @@ "requestedServices": "الخدمات المطلوبة", "selectServices": "اختر الخدمات", "selectedServices": "الخدمات المختارة", - "createNewRequest": "إنشاء طلب جديد", - "youHaveNoPendingRequests": "ليس لديك أي طلبات معلقة.", + "createNewRequest": "طلب جديد", + "youHaveNoPendingRequests": "ليس لديك أي طلبات", "noInsuranceDataFound": "لم يتم العثور على بيانات التأمين...", "noInsuranceUpdateRequest": "لم يتم العثور على أي طلبات لتحديث بيانات التأمين.", "policyNumberInsurancePage": "الوثيقة: {number}", @@ -1277,9 +1306,9 @@ "noInsuranceApprovals": "لم تحصل على أي موافقات تأمينية حتى الآن.", "noInsuranceWithHMG": "ليس لديك تأمين مسجل لدى مجموعة الحبيب الطبية.", "referenceRange": "النطاق المرجعي", - "downloadReport": "تنزيل التقرير", + "downloadReport": "تحميل", "generatingReport": "جارٍ إنشاء التقرير، يرجى الانتظار...", - "noLabResults": "ليس لديك أي نتائج مختبرية حتى الآن.", + "noLabResults": "لا يوجد نتائج", "labResultDetails": "تفاصيل نتائج المختبر", "resultOf": "نتيجة", "whatIsThisResult": "ما هي هذه النتيجة؟", @@ -1289,29 +1318,30 @@ "results": "نتائج", "viewResults": "عرض النتائج", "rebook": "إعادة الحجز", - "noOphthalmologyAppointments": "لم يتم العثور على أي مواعيد في قسم طب العيون...", - "noVitalSignsRecordedYet": "لا توجد علامات حيوية مسجلة بعد", - "appointmentsAndVisits": "المواعيد والزيارات", + "noOphthalmologyAppointments": "لم يتم العثور على أي مواعيد", + "noVitalSignsRecordedYet": "لا توجد علامات حيوية", + "appointmentsAndVisits": "المواعيد", "labAndRadiology": "المختبر والأشعة", "activeMedicationsAndPrescriptions": "الوصفات الطبية", "allPrescriptions": "جميع الوصفات", "allMedications": "جميع الأدوية", - "youDontHaveAnyPrescriptionsYet": "ليس لديك أي وصفات طبية بعد.", - "youDontHaveAnyCompletedVisitsYet": "ليس لديك أي زيارات مكتملة بعد", - "others": "أخرى", + "youDontHaveAnyPrescriptionsYet": "ليس لديك أي وصفات طبية", + "youDontHaveAnyCompletedVisitsYet": "ليس لديك أي زيارات مكتملة", + "others": "خدمات أخرى", "allergyInfo": "معلومات الحساسية", "vaccineInfo": "معلومات اللقاحات", "updateInsuranceInfo": "تحديث التأمين", - "myInvoicesList": "قائمة فواتيري", - "ancillaryOrdersList": "قائمة الطلبات المساعدة", - "youDontHaveAnySickLeavesYet": "ليس لديك أي إجازات مرضية بعد.", + "myInvoicesList": "و الفواتير", + "ancillaryOrdersList": "الخدمات الطبية المساندة", + "youDontHaveAnySickLeavesYet": "ليس لديك أي إجازات مرضية", "medicalReports": "التقارير الطبية", "sickLeaveReport": "تقرير الإجازة المرضية", "weightTracker": "متتبع الوزن", "askYourDoctor": "اسأل طبيبك", "internetPairing": "الاقتران بالإنترنت", "requested": "مطلوب", - "youDontHaveAnyMedicalReportsYet": "ليس لديك أي تقارير طبية بعد.", + "readyToDownload": "جاهز للتحميل", + "youDontHaveAnyMedicalReportsYet": "ليس لديك أي تقارير طبية", "requestMedicalReport": "طلب تقرير طبي", "youDoNotHaveAnyAppointmentsToRequestMedicalReport": "ليس لديك أي مواعيد لطلب تقرير طبي.", "areYouSureYouWantToRequestMedicalReport": "هل أنت متأكد أنك تريد طلب تقرير طبي لهذا الموعد؟", @@ -1330,7 +1360,7 @@ "selectAProfile": "الرجاء تحديد ملف تعريف", "switchFamilyFile": "قم بالتبديل من قائمة الملفات الطبية أدناه", "medicalFiles": "الملفات الطبية", - "addANewFamilyMember": "إضافة فرد جديد من العائلة", + "addANewFamilyMember": "إضافة فرد جديد", "viewInvoiceDetails": "عرض تفاصيل الفاتورة", "outPatient": "مريض خارجي", "invoiceDetails": "تفاصيل الفاتورة", @@ -1346,18 +1376,18 @@ "parkingDate": "التاريخ: {date}", "parkedSince": "متوقف منذ: {time}", "resetDirection": "إعادة تعيين الاتجاه", - "noPrescriptionOrdersYet": "ليس لديك أي طلبات وصفات طبية حتى الآن.", + "noPrescriptionOrdersYet": "ليس لديك أي طلبات.", "fetchingPrescriptionPDFPleaseWait": "جاري جلب ملف الوصفة الطبية، يرجى الانتظار...", "ratingValue": "التقييم: {rating}", "downloadPrescription": "تحميل الوصفة الطبية", "fetchingPrescriptionDetails": "جاري جلب تفاصيل الوصفة الطبية...", "switchBackFamilyFile": "العودة إلى ملف العائلة", - "profileAndSettings": "الملف الشخصي والإعدادات", + "profileAndSettings": "الإعدادات", "quickActions": "إجراءات سريعة", "notificationsSettings": "إعدادات الإشعارات", "touchIDFaceIDServices": "خدمات Touch ID / Face ID", "personalInformation": "المعلومات الشخصية", - "updateEmailAddress": "تحديث عنوان البريد الإلكتروني", + "updateEmailAddress": "عنوان البريد الالكتروني", "helpAndSupport": "المساعدة والدعم", "permissionsProfile": "الأذونات", "privacyPolicy": "سياسة الخصوصية", @@ -1378,9 +1408,9 @@ "previous": "سابق", "selectedOrgans": "الهيئات المختارة", "noOrgansSelected": "لم يتم تحديد أي أعضاء بعد", - "organSelector": "محدد الأعضاء", + "organSelector": "حدد موقع الألم", "noPredictionsAvailable": "لا توجد تنبؤات متاحة", - "areYouSureYouWantToRestartOrganSelection": "هل أنت متأكد أنك تريد إعادة تشغيل اختيار الأعضاء؟", + "areYouSureYouWantToRestartOrganSelection": "هل تريد الخروج؟", "possibleConditions": "الحالات المحتملة", "pleaseSelectAtLeastOneRiskBeforeProceeding": "يرجى اختيار عامل خطر واحد على الأقل قبل المتابعة", "aboveYouSeeCommonRiskFactors": "أعلاه ترى عوامل الخطر الأكثر شيوعًا. على الرغم من أن /diagnosis قد تعيد أسئلة حول عوامل الخطر، ", @@ -1393,10 +1423,10 @@ "failedToLoadImage": "فشل تحميل الصورة", "typeNotification": "النوع", "pleaseSelectAtLeastOneOptionBeforeProceeding": "يرجى اختيار خيار واحد على الأقل قبل المتابعة", - "suggestions": "الاقتراحات", + "suggestions": "التوصيات", "pleaseGoBackAndSelectOrgansFirst": "يرجى العودة واختيار الأعضاء أولاً", "symptomsSelector": "محدد الأعراض", - "possibleSymptomsRelatedTo": "الأعراض المحتملة المتعلقة بـ", + "possibleSymptomsRelatedTo": "أعراض مرتبطة بـ", "suggestionAIListDescription": "هذه قائمة بالأعراض المقترحة من قبل الذكاء الاصطناعي، بناءً على المعلومات التي تم جمعها حتى الآن خلال المقابلة", "analyzing": "جاري التحليل...", "seekMedicalAdvice": "اطلب المشورة الطبية", @@ -1406,23 +1436,23 @@ "colorScience": "دلالات الألوان", "noNeedToVisitDoctor": "لا حاجة لزيارة الطبيب", "commonSymptom": "الأعراض الشائعة", - "bookAppointment": "احجز موعد", + "bookAppointment": "حجز موعد", "notApplicable": "لا ينطبق", "emergencyTriage": "طوارئ", "emergencyEvidenceDetected": "تم اكتشاف دليل طوارئ. يرجى طلب العناية الطبية.", "noQuestionItemsAvailable": "لا توجد عناصر أسئلة متاحة", "pleaseAnswerAllQuestionsBeforeProceeding": "يرجى الإجابة على جميع الأسئلة قبل المتابعة", "triage": "الفرز", - "areYouSureYouWantToExitProgress": "هل أنت متأكد أنك تريد الخروج؟ سيتم فقدان تقدمك.", + "areYouSureYouWantToExitProgress": "هل تود الخروج؟ ستفقد تقدمك الحالي.", "noQuestionAvailable": "لا يوجد سؤال متاح", - "possibleSymptom": "عرض محتمل: ", - "symptomsCheckerFindingScore": "- درجة نتائج فاحص الأعراض", + "possibleSymptom": "تشخيص محتمل", + "symptomsCheckerFindingScore": "نسبة التطابق", "notSet": "غير محدد", "years": "سنوات", - "symptomsChecker": "فاحص الأعراض", + "symptomsChecker": "مدقق الاعراض", "helloIsYourInformationUpToDate": "مرحباً {name}، هل معلوماتك محدثة؟", "noEditAll": "لا، تعديل الكل", - "yesItIs": "نعم، إنها كذلك", + "yesItIs": "نعم", "age": "العمر", "youDontHaveAnyAncillaryOrdersYet": "ليس لديك أي طلبات مساعدة بعد.", "invoiceWithNumber": "الفاتورة: {invoiceNo}", @@ -1448,14 +1478,16 @@ "covered": "مغطى", "vatPercent": "ضريبة القيمة المضافة (15%)", "proceedToPayment": "المتابعة للدفع", - "supportedSmartWatches": "الساعات الذكية المدعومة", + "supportedSmartWatches": "كيف تعمل؟", "pleaseMakeSureSamsungWatchConnected": "يرجى التأكد من أن ساعة Samsung الخاصة بك متصلة بهاتفك، ومتزامنة ومحدثة بشكل نشط.", "beforeSyncingDataFollowInstructions": "قبل مزامنة البيانات، يرجى التأكد من اتباع التعليمات بشكل صحيح.", "viewWatchInstructions": "عرض تعليمات الساعة", "healthConnectAppNotInstalled": "يبدو أنه ليس لديك تطبيق Health Connect مثبتًا. يرجى تثبيته من متجر Play لمزامنة بيانات صحتك.", - "setTimerOfReminder": "ضبط مؤقت التذكير", + "setTimerOfReminder": "تنبيهي قبل الموعد", "youHaveAppointmentWithDr": "لديك موعد مع د. ", "hours": "ساعات", + "minutes": "دقائق", + "seconds": "ثواني", "secs": "ثواني", "noAllergiesDataFound": "لم يتم العثور على بيانات الحساسية...", "heartRateDescription": "معدل ضربات قلبك يشير إلى عدد المرات التي ينبض فيها قلبك في الدقيقة", @@ -1465,6 +1497,7 @@ "distanceDescription": "المسافة المقطوعة على مدار اليوم", "overview": "نظرة عامة", "details": "التفاصيل", + "details2": "تفاصيل", "healthy": "صحي", "warning": "تحذير", "vitalSignDetails": "تفاصيل العلامات الحيوية", @@ -1515,7 +1548,7 @@ "notifyMeBeforeConsumptionTime": "أبلغني قبل وقت الاستهلاك", "noMedicationsToday": "لا أدوية اليوم", "reminders": "تذكيرات", - "reminderAddedToCalendar": "تمت إضافة تذكير إلى التقويم ✅", + "reminderAddedToCalendar": "تمت إضافة تذكير إلى التقويم ", "errorWhileSettingCalendar": "حدث خطأ أثناء ضبط التقويم:{error}", "instructions": "التعليمات", "requests": "الطلبات", @@ -1527,28 +1560,28 @@ "pleaseVisitRoomForVitalSigns": "يرجى زيارة الغرفة {roomNumber} لقياس العلامات الحيوية", "pleaseVisitRoomToTheDoctor": "يرجى زيارة الغرفة {roomNumber} لمقابلة الطبيب", "erOnlineCheckInRequest": "طلب تسجيل الوصول عبر الإنترنت لقسم الطوارئ", - "indoor": "داخلي", - "navigation": "الملاحة", - "health": "الصحة", + "indoor": "خريطة", + "thehospital": "المستشفى", + "health": " الصحية", "calculators": "الحاسبات", - "converters": "المحولات", - "guide": "الدليل", - "liveCareServiceDesc": "احصل على استشارة طبية من طبيب متخصص عبر مكالمة فيديو", + "converters": "محول الوحدات", + "guide": "دليل", + "liveCareServiceDesc": "احصل على استشارة طبية من طبيب مختص عبر مكالمة فيديو", "bloodGroup": "فصيلة الدم: {bloodType}", "medicalServices": "الخدمات الطبية", - "insuranceAndPayments": "التأمين والمدفوعات", + "insuranceAndPayments": "التأمين و الفواتير", "trackerAndOthers": "المتتبع وأخرى", "bmi": "مؤشر كتلة الجسم", - "yesPleaseINeedHelp": "نعم، من فضلك أريد المساعدة", + "yesPleaseINeedHelp": "نعم، احتاج مساعدة", "aiOverView": "AI ملخص", - "generateAiAnalysis": "قم بإجراء تحليل لهذا الطلب AI", - "aiAnalysis": "AI تحليل", - "prediction": "تنبؤ", - "prevention": "وقاية", - "disclaimer": "تنصل", - "thisAboveInfo": "المعلومات المذكورة أعلاه مُولّدة بواسطة AI بناءً على نتائج تحاليلك المخبرية، وهي لأغراض إعلامية فقط. لا ينبغي اعتبارها تشخيصًا طبيًا أو خطة علاجية. استشر طبيبك دائمًا للحصول على نصيحة طبية مُخصصة قبل اتخاذ أي قرارات تتعلق بصحتك.", + "generateAiAnalysis": "تحليل الذكاء الاصطناعي", + "aiAnalysis": "تحليل الذكاء الاصطناعي", + "prediction": "التوقعات", + "prevention": "الوقاية", + "disclaimer": "إخلاء مسؤولية", + "thisAboveInfo": "تم تحليل نتائج المختبرهذه بواسطة الذكاء الاصطناعي، وهي لا تُعدّ نصيحة طبية. استشر طبيبك المختص للتشخيص والعلاج.", "aiDisclaimer": "سيتم مشاركة بيانات نتائج تحاليلك المخبرية بشكل آمن مع خدمة الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في تقديم رؤى صحية مُخصصة. هل ترغب في المتابعة؟", - "consent": "موافقة", + "consent": "تحليل الذكاء الاصطناعي", "bmiNormalDescription": "مؤشر كتلة جسمك ضمن المعدل الصحي. استمر في اتباع نظام غذائي متوازن، وممارسة النشاط البدني بانتظام، وإجراء الفحوصات الدورية للحفاظ على صحة جيدة. راقب أي تغييرات ملحوظة مع مرور الوقت.", "bmiUnderweightDescription": "يشير مؤشر كتلة الجسم إلى أنك تعاني من نقص الوزن. لذا، يُنصح بزيادة السعرات الحرارية المتناولة من خلال تناول الأطعمة الغنية بالعناصر الغذائية، وممارسة تمارين المقاومة لبناء العضلات، واستشارة أخصائي رعاية صحية للتحقق من وجود أسباب كامنة.", "bmiOverweightDescription": "يشير مؤشر كتلة الجسم إلى زيادة الوزن. لذا، يُنصح بالسعي إلى إنقاص الوزن تدريجياً وبشكل مستدام من خلال تقليل السعرات الحرارية بشكل معتدل، وممارسة النشاط البدني بانتظام، واختيار الأطعمة الصحية. استشر طبيبك قبل إجراء أي تغييرات جذرية في نمط حياتك.", @@ -1587,10 +1620,10 @@ "servicePriceListRights": "يحق للمريض الحصول على متابعة مجانية في غضون 14 يومًا من الزيارة الأولى", "serviceName": "اسم الخدمة", "noFreeSlot": "لا توجد مواعيد متاحة في التاريخ المحدد. يرجى اختيار تاريخ آخر أو إعادة المحاولة لاحقاً.", - "noThanksIKnowTheClinic": "لا، شكراً. أنا أعرف العيادة.", + "noThanksIKnowTheClinic": "لا، شكراً", "unableToSendOTP": "تعذر إرسال رمز التحقق لمرة واحدة", - "loadingAIAnalysis": "نقوم بتحليل نتائجك، يرجى التحلي بالصبر والسماح للذكاء الاصطناعي بالقيام بالمهمة، قد يستغرق هذا بعض الوقت.", - "symptoms": "أعراض", + "loadingAIAnalysis": "جارٍ تحليل النتائج بواسطة الذكاء الاصطناعي، يرجى الانتظار...", + "symptoms": "مدقق الاعراض", "insuranceInActive": "التأمين غير نشط", "insuranceInActiveContactSupport": "لديك تأمين ساري المفعول، ولكنه غير مُفعّل حاليًا في مجموعة حبيب الطبية. يُرجى التواصل مع خدمة عملاء مجموعة حبيب الطبية.", "updateAppNow": "تحديث التطبيق الآن", @@ -1616,23 +1649,22 @@ "continueCash": "متابعة الدفع نقدًا", "timeFor": "الوقت", "hmgPolicies": "سياسات مجموعة الحبيب الطبية", - "darkMode": "المظهر الداكن", + "darkMode": "الوضع الداكن", "featureComingSoonDescription": "هذه الميزة ستتوفر قريباً. نحن نعمل جاهدين لإضافة ميزات أكثر تميزاً إلى التطبيق. انتظرونا لمتابعة التحديثات.", - "generateAiAnalysisResult": "قم بإجراء تحليل لهذا المختبر AI", + "generateAiAnalysisResult": "تحليل النتائج بالذكاء الاصطناعي", "ratings": "التقييمات", - "hmgPharmacyText": "صيدلية الحبيب، المتجر الصيدلاني الإلكتروني المتكامل الذي تقدمه لكم مجموعة خدمات الدكتور سليمان الحبيب الطبية.", + "hmgPharmacyText": "وجهتك الالكترونية الشاملة للأدوية ومنتجات العناية بالصحة", "insuranceRequestSubmittedSuccessfully": "تم إرسال طلب تحديث بيانات التأمين بنجاح. سيتم إعلامك بمجرد الانتهاء.", "updatingEmailAddress": "جارٍ تحديث عنوان البريد الإلكتروني، يرجى الانتظار...", "verifyInsurance": "التحقق من التأمين", - "tests": "تحليل", + "tests": "تحاليل", "calendarPermissionAlert": "يرجى منح إذن الوصول إلى التقويم من إعدادات التطبيق لضبط تذكير تناول الدواء.", - "sortByNearestLocation": "فرز حسب الأقرب إلى موقعك", + "sortByNearestLocation": "حسب الأقرب الى موقعك", "giveLocationPermissionForNearestList": "يرجى منح إذن الوصول إلى الموقع من إعدادات التطبيق لعرض أقرب المواقع.", "sortByLocation": "الترتيب حسب الموقع", - "timeForFirstReminder": "وقت التذكير الأول", - "reminderRemovalNote": "يمكنك إزالتها من التقويم الخاص بك لاحقاً عن طريق إيقاف تشغيل التذكير", + "timeForFirstReminder": "اضافةتذكير", + "reminderRemovalNote": "يمكنك إلغاء التذكير لاحقاً من التقويم.", "communicationLanguage": "لغة التواصل", - "cmcServiceHeader": "فحص صحي شامل: تشخيص متقدم، معلومات صحية مفصلة", "cmcServiceDescription": "احصل على معلومات تفصيلية عن صحتك من خلال خدمات التشخيص المتقدمة لدينا. افهم جسمك بشكل أفضل لمستقبل صحي.", "eReferralServiceHeader": "نظام الإحالة الإلكترونية في مستشفى حبيب: تبسيط عملية إحالة المرضى", @@ -1650,19 +1682,19 @@ "emergencyServiceHeader": "تسجيل الوصول إلى قسم الطوارئ، أسرع من أي وقت مضى. اتصل بالإسعاف / فريق الاستجابة السريعة على الفور", "emergencyServiceDescription": "هل تواجه حالة طبية طارئة؟ سيارات الإسعاف وفرق الاستجابة السريعة لدينا جاهزة على مدار الساعة. بالإضافة إلى ذلك، يمكنك تسجيل دخولك إلى قسم الطوارئ بسرعة لتلقي رعاية أسرع.", "homeHealthCareServiceHeader": "صحتك، في أبهى صورها. رعاية فائقة الجودة، تصلك إلى عتبة دارك.", - "homeHealthCareServiceDescription": "نقدم لكم رعاية صحية عالية الجودة تصلكم إلى عتبة منزلكم. ممرضات ذوات خبرة يقدمون رعاية حانية في راحة منزلكم.", + "homeHealthCareServiceDescription": "نقدم لكم رعاية صحية عالية الجودة تصلكم إلى عتبة منزلكم. ممرضات ذوات خبرةممرضات ذوات خبرة يقدمون لكم الرعاية اللازمة.", "profileOnlyText": "الملف الشخصي", - "information": "معلومة", + "information": "معلومات الطبيب", "noFavouriteDoctors": "ليس لديك أي قائمة مفضلة حتى الآن", "addDoctors": "إضافة الأطباء", "favouriteList": "قائمة المفضلة", "later": "لاحقاً", "cancelAppointmentConfirmMessage": "هل أنت متأكد من رغبتك في إلغاء هذا الموعد؟", - "acknowledged": "معترف به", + "acknowledged": "إغلاق", "searchLabResults": "بحث نتائج المختبر", "callForAssistance": "اتصل للحصول على المساعدة الفورية", "oneWaySubtitle": "نقل من الموقع إلى المستشفى", - "twoWaySubtitle": "نقل من الموقع إلى المستشفى والعودة مرة أخرى", + "twoWaySubtitle": "نقل من الموقع إلى المستشفى والعودة", "toHospitalSubtitle": "نقل من موقعك الحالي إلى المستشفى", "fromHospitalSubtitle": "نقل من المستشفى إلى منزلك", "clinicSchedule": "جدول العيادة", @@ -1673,18 +1705,18 @@ "pendingAncillaryOrders": "مدفوعات طلبات الاضافية", "liveCarePermissions": "يتطلب لايف كير أذونات الكاميرا والميكروفون وإشعارات، يرجى السماح لها بالمتابعة.", "containsImage": "يحتوي على صورة", - "servicesSelected": "الخدمة (الخدمات) المختارة", + "servicesSelected": "الخدمة / الخدمات", "selectMedicalFileFromFamily": "اختر ملف طبي من عائلتك", "anyFileFromHMG": "أي ملف طبي نشط من مجموعة حبيب الطبية", "noDistance": "المسافة غير متوفرة", "enterAmount": "يرجى إدخال المبلغ للمتابعة.", "selectHospitalContinue": "يرجى اختيار المستشفى للمتابعة.", "year": "سنة", - "notifyPrescription": "أبلغني قبل وقت الاستهلاك", + "notifyPrescription": "تنبيه قبل موعد الجرعة", "doctorRatingAppointment": "طبيب\nتصنيف", "ratingSubmitted": "تم إرسال التقييم", "rateTheDoctor": "يرجى تقييم الطبيب", - "downloadInvoice": "تحميل الفاتورة", + "downloadInvoice": "تحميل", "approvalDetails": "تفاصيل الموافقة", "obese": "سمين", "overWeight": "وزن زائد", @@ -1699,7 +1731,7 @@ "openCamera": "افتح الكاميرا", "openGallery": "افتح المعرض", "openFiles": "فتح الملفات", - "generateAiAnalysisRadResult": "بإنشاء تحليل الذكاء الاصطناعي لهذا التقرير", + "generateAiAnalysisRadResult": "النتيجة بالذكاء الاصطناعي", "grantLocationPermission": "يرجى منح إذن الوصول إلى الموقع من إعدادات التطبيق للحصول على نتائج أفضل.", "NoBookedAppointments": "لا توجد مواعيد محجوزة", "aiDisclaimerRad": "سيتم مشاركة بيانات نتائج الأشعة الخاصة بك بشكل آمن مع محلل الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في توفير رؤى صحية مخصصة. هل ترغب في المتابعة؟", @@ -1720,5 +1752,47 @@ "aiDisclaimerRad": "سيتم مشاركة بيانات نتائج الأشعة الخاصة بك بشكل آمن مع محلل الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في توفير رؤى صحية مخصصة. هل ترغب في المتابعة؟", "sendingAskDrRequest": "جارٍ إرسال الطلب...", "askDrRequestSentSuccess": "تم إرسال الطلب بنجاح، وسيتواصل معك الطبيب قريباً.", - "onlineCheckIn24HoursOnly": "لا تتوفر خدمة تسجيل الوصول عبر الإنترنت إلا خلال 4 ساعات من موعد الحجز.لا تتوفر خدمة تسجيل الوصول عبر الإنترنت إلا خلال 4 ساعات من موعد الحجز." + "onlineCheckIn24HoursOnly": "لا تتوفر خدمة تسجيل الوصول عبر الإنترنت إلا خلال 4 ساعات من موعد الحجز.لا تتوفر خدمة تسجيل الوصول عبر الإنترنت إلا خلال 4 ساعات من موعد الحجز.", + "searchFor": "بحث عن", + "m": "متر", + "inch": "انش", + "updateEmailAddressTitle": "أدخل البريد الإلكتروني الجديد: ", + "enterNewNewContactNumber": "الرجاء اضافة رقم اتصال في حالة الطوارئ: ", + "nationalIdFileNumber": "رقم الهوية / الإقامة / الملف", + "finda": "البحث عن", + "errorFetchingData": "حدث خطأ أثناء تحميل البيانات", + "negative": "سلبي", + "calculateCaloriesBasedOnFactors": "حساب السعرات الحرارية اليومية بناءً على عدة عوامل مثل الطول، الوزن، العمر، الجنس، والنشاط البدني اليومي.", + "conformationCall": "سنتصل بك قريبًا لتأكيد الطلب", + "radiologyLabResults": "نتائج الأشعة", + "disclaimerRadiology": "تم تحليل نتائج الأشعة هذه بواسطة الذكاء الاصطناعي، وهي لا تُعدّ نصيحة طبية. استشر طبيبك المختص للتشخيص والعلاج.", + "enterIdentificationNumberWithNoStar": "أدخل رقم الهوية", + "enterReferralNumber": "أدخل رقم الإحالة", + "referralNumber": "رقم الإحالة", + "phoneHint": "5xxxxxxxx", + "selectSearchCriteria": "اختر معيار البحث", + "selectCriteria": "اختيار المعيار", + "locationServicesDisabled": "حالة الطقس غير متوفرة لتعطيل خدمات الموقع. يرجى تفعيل خدمات الموقع لتتمكن من رؤية معلومات الطقس.", + "selectDateTimeKey": "اختيار", + "medicalKey": "الطبية", + "doctors2": "أطباء", + "illurgyInfomation": "الحساسية", + "vaccineInfomation": "التطعيمات", + "approvals1": "موافقات", + "convertBloodcholesterolInfo":"حول قيمة كوليسترول الدم بين ملجم/ديسيلتر و مليمول/لتر", + "carbsAlert": "حساب نسب المغذيات (الكربوهيدرات ،البروتين والدهون) بالسعرات الحرارية والقرامات وفقا لنسب محددة مسبقا", + "bodyFatAlert": "يحسب بناءً على الحجم", + "ibwAlert": "حساب الوزن المثالي بناءً على الطول، الوزن، وبنية الجسم", + "glucoseAlert":"تغيير وحدات القياس بين مليجرام لكل ديسيلتر و ملي مول لكل لتر", + "selectBloodGroup": "اختر الفصيلة", + "routePrescription": "طريقة الاستخدام", + "noNearestER": "لا يوجد طوارئ قريب منك", + "loginToUseServiceLiveCare": "تحتاج إلى تسجيل الدخول لاستخدام هذه الخدمة", + "mldl": "مليجرام لكل ديسيلتر", + "mmol": "ملي مول لكل لتر", + "triglycerideInfo": "تغيير وحدات القياس بين مليجرام لكل ديسيلتر و ملي مول لكل لتر", + "selectSmartWatch": "تعيين", + "selectBloodDonationCity": "اختر المدينة", + "selectBloodDonationGender": "اختر الجنس", + "selectBloodType": "اختر الفصيلة" } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 18484fc1..27373b5f 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -6,7 +6,7 @@ "noRatingAvailable": "No Rating Available", "doctorDoesNotHaveRating": "This doctor does not have any ratings yet.", "confirm": "Confirm", - "loadingText": "Loading, please wait...", + "loadingText": "Please wait...", "kilometerUnit": "KM", "dashboardScreenToolbarTitle": "Home", "settings": "Settings", @@ -21,7 +21,7 @@ "mySchedule": "My Schedule", "logout": "Logout", "respirationRate": "Respiration Rate", - "bookAppo": "Book Appointment", + "bookAppo": "New Appointment", "searchBy": "Search By:", "clinic": "Clinic", "byClinic": "By Clinic", @@ -40,7 +40,7 @@ "noResultFound": "No Result Found", "noSearchResultFound": "No Result Found", "pleaseEnterProductName": "Please Enter Product Name", - "bookNow": "Book Now", + "bookNow": "New Appointment", "docInfo": "Doctor Information", "appoInfo": "Appointment Information", "availableAppo": "Available Appointments", @@ -69,7 +69,7 @@ "date": "Date", "time": "Time", "pendingPayment": "Pending for payment", - "payNow": "Pay Now", + "payNow": "Payment", "viewQR": "View QR Code", "instruction": "Instructions", "livecare": "LiveCare", @@ -99,7 +99,7 @@ "onlinePaymentSubtitle": "Payment", "cancelNocaps": "Cancel", "setReminder": "Set Reminder", - "nationalIdNumber": "National ID Number", + "nationalIdNumber": "National ID / Iqama", "medicalFileNumber": "Medical File Number", "loginregister": "Login / Register", "poweredBy": "Powered by", @@ -112,7 +112,7 @@ "yes": "Yes", "no": "No", "logintyperadio": " Choose from the below options to login to your medical file.", - "registernow": "Register Now", + "registernow": "Register", "nationalID": "Enter the Identification Number", "nationalId": "National ID", "fileNo": "File Number", @@ -123,7 +123,7 @@ "profileInfo": "Please enter profile information", "submit": "Submit", "forgotDesc": "Enter the mobile number to receive the Medical File Number via SMS", - "dob": "Birth Date:", + "dob": "Birth Date", "hijriDate": "Hijri Date", "gregorianDate": "Gregorian Date", "verifyLoginWith": "Please choose one of the following options to verify", @@ -196,6 +196,7 @@ "enterEmail": "Please Enter Email", "family": "My Family", "familyTitle": "My Family Files", + "familyTitle2": "My Family Files", "addNewMember": "Add Family Member", "sentRequests": "Sent Requests", "recievedRequests": "Recieved Requests", @@ -268,6 +269,7 @@ "userViewRequester": "User Wants to View Your Medical File", "userView": "User Can View Your Medical File", "parking": "Parking", + "parking2": "Parking", "alhabiServices": "HMG Service", "parkingTitle": "Car service, car service, service to save parking information, return to it later, 1- By clicking on (Read the code), save the parking data. 2- By clicking on the button (view my car park), it shows you the car’s location in Google Maps. 3- Read another position by pressing the Clear Position Data button.", "parkingDescription": "Parking service is for you to scan the car location so you can find it easy on you way out. click on (scan parking) button to save the parking location, then (show my park) button will appear to show you the way for the parking. if you want to rescan parking QR just click on (Clear My Data).", @@ -306,10 +308,12 @@ "vitalSignObese": "Obese", "vitalSignOverweight": "Overweight", "vitalSignUnderweight": "Underweight", + "vitalSignSkinny": "Underweight", "myMedical": "Active", "myMedicalSubtitle": "Medications", "myDoctor": "My Doctors", "myDoctorSubtitle": "List", + "myDoctorSubtitle2": "List", "eye": "Eye", "eyeSubtitle": "Measurement", "insurance": "Insurance", @@ -327,6 +331,7 @@ "monthly": "Monthly", "monthlySubtitle": "Report", "sick": "Sick", + "sick2": "Sick", "sickSubtitle": "Leaves", "myBalance": "My Balance", "myBalanceSubtitle": "Credit", @@ -438,7 +443,7 @@ "serviceInformation": "Service Information", "homeHealthCare": "Home Health Care", "noAppointmentAvailable": "No Available Appointments", - "homeHealthCareText": "This service provides a set of home health care services, continuous and comprehensive follow-up in their places of residence for those who cannot access health facilities, such as (laboratory analyzes - radiology - vaccinations - physical therapy), etc.", + "homeHealthCareText": "Clinical excellence and specialized care in the comfort of your home", "loginRegister": "Login/Register", "orderLog": "Order Log", "infoLab": "This service allows you to view the results of all laboratory tests performed in Al Habib Medical Group as well as sending the report via e-mail.", @@ -484,8 +489,10 @@ "refferalSubTitle": "Service", "healthCare": "Health Care", "emergency": "Emergency", + "emergency2": "Emergency", "erservices": "Emergency", "services2": "Services", + "services3": "Services", "cantSeeProfile": "To view your medical profile, please log in or register now", "loginRegisterNow": "Login or Register Now", "hmgPharmacy": "HMG Pharmacy", @@ -694,11 +701,11 @@ "bikini": "Bikini", "totalMinutes": "Total Minutes", "feedback": "Feedback", - "likeToHear": "We would love to hear the feedback, concerns on healthcare services and eServices experience. Please use the below form", + "likeToHear": "We would love to hear your feedback. If you have any concerns about healthcare services or eServices experience, please let us in the form below.", "emptySubject": "Please enter the subject", "emptyMessage": "Please enter message", "selectAttachment": "Select Attachment", - "complainAppo": "Complaint for appointment", + "complainAppo": "Complaint with appointment", "complainAppoAlt": "Complaint without appointment", "messageType": "Message Type", "feedbackType": "Feedback Type", @@ -757,6 +764,7 @@ "erConsultation": "This service allows you to make an online virtual consultation via video call directly with the doctor from anywhere at any time.", "myInvoice": "List", "invoiceList": "My Invoice", + "invoiceList2": "My Invoice", "allInvoices": "All Invoices", "hospitals": "Hospitals", "clinics": "Clinics", @@ -769,7 +777,7 @@ "livecarePoint2": "A Specialized doctor will contact you", "livecarePoint3": "A Doctor will be able to see your full medical file history", "livecarePoint4": "Free Medicine Delivery Available", - "livecarePoint5": "No need to visit the service is in your place", + "livecarePoint5": "Consult our specialists remotely and manage your health whenever and wherever you are.", "bookImmediateLivecare": "I accept to get the service immediately", "bookVideoLivecare2": "I don't need to visit the clinic", "noThankyou": "No Thanks", @@ -783,11 +791,11 @@ "aboutApp": "About the app", "aboutPoints": "Online Appointment Booking & rescheduling, Insurance approval status, Find A doctor, Ask your doctor, Medical prescriptions, Lab results, Hospitals contact numbers, Doctor profiles, Hospitals locations, Pharmacies Locations, Hospital's Virtual Tour, Official Social Media, Vaccines Schedule, Health Calculators, Other Services", "termsConditions": "These Online Services Terms of Use (Service Terms) govern certain online services provided by Dr Sulaiman Al Habib Medical Services Group Company (HMG, we, us, our)...", - "receiveOtpToast": "Where would you like to receive OTP?", + "receiveOtpToast": "OTP", "enterPhoneNumber": "Enter Phone Number", "enterEmailDesc": "Enter your email address to complete the process of creating a medical file", "enterPhoneDesc": "Enter your phone number to receive OTP verification code", - "pleaseChooseOption": "Please select from the below options to receive OTP", + "pleaseChooseOption": "Please select an option below to receive your OTP.", "dontHaveAccount": "Don't have an account?", "loginOrRegister": "Login or Register", "myFiles": "My Files", @@ -798,14 +806,14 @@ "readInstructions": "Read Instructions", "searchLabReport": "Search Lab Report", "prescriptionDeliveryError": "This clinic doesn't support refill", - "prepareToElevate": "Prepared to elevate your health and well-being?", + "prepareToElevate": "Your wellness journey starts here.", "iAcceptTermsConditions": "I Accept the Terms and Conditions", "alreadyHaveAccount": "Already have an account?", "loginNow": "Login Now", "notice": "Notice", "oR": "OR", - "sendOTPWHATSAPP": "Send me OTP on Whatsapp", - "sendOTPSMS": "Send me OTP on SMS", + "sendOTPWHATSAPP": "Whatsapp", + "sendOTPSMS": "SMS", "fullName": "Full Name", "married": "Married", "uae": "United Arab Emirates", @@ -814,8 +822,10 @@ "loginByOTP": "Login By OTP", "guest": "Guest", "switchAccount": "Switch Account", - "allSet": "All Set! Now you can login with Face ID or Biometric", + "allSet": "Now you can login with Face ID or Biometric", + "quickLogin": "Quick Login", "enableQuickLogin": "Enable Quick Login", + "enableQuickLoginBtn": "Enable Quick Login", "enableMsg": "Enabling the quick login will verify through your existing device Face ID / Biometric", "notNow": "Not Now", "pendingActivation": "Pending Activation", @@ -841,7 +851,7 @@ "didntReceiveIt": "Didn't receive it?", "resendOTP": "Resend", "resendIn": "resend in", - "pleaseEnterAnationalID": "Please enter a national ID", + "pleaseEnterAnationalID": "ID or Iqama number is required", "pleaseEnterAFileNumber": "Please enter a file number", "pleaseEnterAValidEmail": "Please enter a valid email", "pleaseEnterFullName": "Please enter full name", @@ -870,6 +880,7 @@ "monitor": "Monitor", "noSpecialResult": "No Special Results", "setTheDateRange": "Set The Date Range", + "selectDate": "Select Date", "historyFlowchart": "History FlowChart", "to": "to", "startDate" : "Start Date", @@ -882,7 +893,7 @@ "clearAllFilters": "Clear all filters", "filters": "Filters", "searchClinic": "Search Clinic", - "walkin": "Walk In", + "walkin": "Booked", "continueString": "Continue", "laserClinic": "Laser Clinic", "covid_info" :"Dr. Sulaiman Al Habib hospitals are conducting a test for the emerging corona virus and issuing travel certificates 24/7 in a short time and with high accuracy. Those wishing to benefit from this service can visit one of Dr. Sulaiman Al Habib branches to conduct a corona test within few minutes, and obtain the result within several hours. Corona Virus Covid 19 testing service with PCR technology to detect the virus according to the highest international standards and with the latest high-precision RT-PCR devices (American GeneXpert and others), That is approved by the Food and Drug Authority as well as by the Saudi Center for Infectious Diseases Prevention.", @@ -910,7 +921,7 @@ "paymentFailedPleaseTryAgain": "Payment Failed! Please try again.", "appointmentCheckIn": "Appointment check in", "insuranceExpiredOrInactive": "Insurance expired or inactive", - "totalAmountToPay": "Total amount to pay", + "totalAmountToPay": "Total Amount", "vat15": "VAT 15%", "general": "General", "liveCare": "LiveCare", @@ -925,15 +936,15 @@ "immediateConsultation": "Immediate Consultation", "scheduledConsultation": "Scheduled Consultation", "pharmaLiveCare": "Pharma LiveCare", - "notSureHelpMeChooseClinic": "Not sure? help me choose a clinic!", - "mentionYourSymptomsAndFindDoctors": "Mention your symptoms and find the list of doctors accordingly", + "notSureHelpMeChooseClinic": "Need help choosing a clinic?", + "mentionYourSymptomsAndFindDoctors": "Tell us your symptoms, and we’ll recommend the best-suited specialists for your care", "immediateService": "Immediate service", "noNeedToWaitGetMedicalConsultation": "No need to wait, you will get medical consultation immediately via video call", "noVisitRequired": "No visit required", - "doctorWillContact": "Doctor will contact", - "specialisedDoctorWillContactYou": "A specialised doctor will contact you and will be able to view your medical history", + "doctorWillContact": "Doctor will contact you", + "specialisedDoctorWillContactYou": "A specialized doctor will contact you and will be able to view your medical history", "freeMedicineDelivery": "Free medicine delivery", - "offersFreeMedicineDelivery": "Offers free medicine delivery for the LiveCare appointment", + "offersFreeMedicineDelivery": "Receive free medicine delivery with your LiveCare appointment.", "dentalChiefComplaints": "Dental Chief Complaints", "viewAvailableAppointments": "View available appointments", "doctorProfile": "Doctor Profile", @@ -952,22 +963,22 @@ "viewNearestAppos": "View nearest available appointments", "noDoctorFound": "No Doctor found for selected criteria...", "yesPleasImInAHurry": "Yes please, I am in a hurry", - "fetchingFeesPleaseWait": "Fetching fees, Please wait...", + "fetchingFeesPleaseWait": "Please wait...", "noThanksPhysicalVisit": "No, Thanks. I would like a physical visit", "offline": "Offline", "videoCall": "Video Call", - "liveVideoCallWithHMGDoctors": "Live Video Call with HMG Doctors", - "liveAudioCallWithHMGDoctors": "Live Audio Call with HMG Doctors", + "liveVideoCallWithHMGDoctors": "Live video call with a specialist", + "liveAudioCallWithHMGDoctors": "Live audio call with a specialist", "audioCall": "Audio Call", "phoneCall": "Phone Call", - "livePhoneCallWithHMGDoctors": "Live Phone Call with HMG Doctors", - "reviewLiveCareRequest": "Review LiveCare Request", - "selectedLiveCareType": "Selected LiveCare Type", - "selectLiveCareCallType": "Select LiveCare call type", + "livePhoneCallWithHMGDoctors": "Live phone call with a specialist", + "reviewLiveCareRequest": "LiveCare Request Details", + "selectedLiveCareType": "LiveCare Type", + "selectLiveCareCallType": "LiveCare Call Type", "confirmingLiveCareRequest": "Confirming LiveCare request, Please wait...", "unknownErrorOccurred": "Unknown error occurred...", "liveCarePermissionsMessage": "LiveCare requires Camera, Microphone, Location & Notifications permissions to enable virtual consultation between patient & doctor, Please allow these to proceed.", - "liveCarePayment": "LiveCare Payment", + "liveCarePayment": "Payment", "mada": "Mada", "visaOrMastercard": "Visa or Mastercard", "tamara": "Tamara", @@ -990,7 +1001,7 @@ "bookingWaitingAppointment": "Booking Waiting Appointment, Please wait...", "enterValidIDorIqama": "Please enter a valid national ID or file number", "selectAppointment": "Select Appointment", - "rebookSameDoctor": "Rebook with same doctor", + "rebookSameDoctor": "New Appointment", "queueing": "Queueing", "inQueue": "In Queue", "yourTurn": "Your Turn", @@ -1051,7 +1062,7 @@ "insuranceDocument": "Insurance Document", "selectBranch": "Select Branch", "patientInformation": "Patient information", - "patientLocation": "Where the patient located", + "patientLocation": "Select the location of the patient", "identificationNumber": "Identification Number", "enterIdentificationNumber": "Enter Identification Number*", "patientName": "Patient Name*", @@ -1079,7 +1090,7 @@ "hereIsYourReferralNumber": "Here is your Referral #: ", "searchEReferral": "Search E-Referral", "enterRequiredInfoToSearch": "Please enter the required information to search for an e-referral", - "selectPickupDirection": "Select Pickup Direction", + "selectPickupDirection": "Pickup Direction", "selectDirection": "Select Direction", "toHospital": "To Hospital", "fromHospital": "From Hospital", @@ -1090,9 +1101,9 @@ "pleaseSelectDetailsOfPickup": " Please select the details of pickup", "selectDetails": "Select Details", "work": "Work", - "pick": "Pick", - "insideTheHome": "Inside the home", - "haveAnyAppointment": "Have any appointment", + "pick": "Pick up", + "insideTheHome": "From your home/Selected location", + "haveAnyAppointment": "Select if you have any appointment?", "amountPaidAtHospital": "Amount will be paid at the hospital", "submitRequest": "Submit Request", "enterPickupLocationManually": "Enter Pickup Location Manually", @@ -1125,26 +1136,33 @@ "allFacilities": "All Facilities", "selectLocation": "Select Location", "pleaseSelectTheLocation": "Please select the location", - "viewLocationGoogleMaps": "View Location on Google Maps", + "viewLocationGoogleMaps": "Get Directions on Google Maps", "callAmbulance": "Call Ambulance", - "requestAmbulanceInEmergency": "Request an ambulance in emergency from home or hospital", + "requestAmbulanceInEmergency": "Request an ambulance for immediate assistance in cases of emergency.", "confirmation": "Confirmation", "areYouSureYouWantToCallAmbulance": "Are you sure you want to call an ambulance?", - "getDetailsOfNearestBranch": "Get the details of nearest branch including directions", + "getDetailsOfNearestBranch": "Find the nearest ER and get directions.", "areYouSureYouWantToCallRRT": "Are you sure you want to call Rapid Response Team (RRT)?", - "priorERCheckInToSkipLine": "Prior ER Check-In to skip the line & payment at the reception.", - "areYouSureYouWantToMakeERCheckIn": "Are you sure you want to make ER Check-In?", - "checkingYourERAppointmentStatus": "Checking your ER Appointment status...", + "priorERCheckInToSkipLine": "Check in before you arrive to the ER to skip the line & payment at reception.", + "areYouSureYouWantToMakeERCheckIn": "Are you sure you want to request an online ER check-in?", + "checkingYourERAppointmentStatus": "Please Wait...", "transportOptions": "Transport Options", "selectHospitalForAdvancePayment": "Please select the hospital you want to make an advance payment for.", - "recharge": "Recharge", + "recharge": "Add Funds", "activityLevel": "Activity Level", "selectActivityLevel": "Select Activity Level", + "almostInactive": "Almost Inactive (no exercise)", + "lightlyActive": "Lightly active", + "lightlyActive13Days": "Lightly active (1-3) days per week", + "superActive": "Super active (very hard exercise)", "caloriesPerDay": "Calories Per Day", "dietType": "Diet Type", "selectDietType": "Select Diet Type", "bodyFrameSize": "Body Frame Size", "selectBodyFrameSize": "Select Body Frame Size", + "bodyFrameSmall": "Small (fingers overlaps)", + "bodyFrameMedium": "Medium (fingers touch)", + "bodyFrameLarge": "Large (fingers don't touch)", "averageCycleLength": "Average Cycle Length (Usually 28 days)", "averageLutealPhase": "Average Luteal Phase Length(Usually 14 days)", "convert": "Convert", @@ -1162,14 +1180,22 @@ "monitorCholesterolLevels": "Monitor cholesterol levels, assess heart health risks, and take proactive steps for well-being.", "triglyceridesFatBlood": "Triglycerides Fat Blood", "understandTriglyceridesImpact": "Understand triglycerides' impact on heart health with personalized insights and expert recommendations.", - "bmiCalculator": "BMI\nCalculator", - "caloriesCalculator": "Calories\nCalculator", - "bmrCalculator": "BMR\nCalculator", - "idealBodyWeight": "Ideal Body\nWeight", - "bodyFatCalculator": "Body Fat\nCalculator", - "carbsProteinFat": "Carbs\nProtein & Fat", - "ovulationPeriod": "Ovulation\nPeriod", - "deliveryDueDate": "Delivery\nDue Date", + "bmiCalculator": "BMI Calculator", + "bmiCalculatorDesc": "Calculate your body mass index to understand your healthy weight range.", + "caloriesCalculator": "Calories Calculator", + "caloriesCalculatorDesc": "Find out how many calories you need daily to maintain or lose weight.", + "bmrCalculator": "BMR Calculator", + "bmrCalculatorDesc": "Determine your basal metabolic rate and daily energy requirements.", + "idealBodyWeight": "Ideal Body Weight", + "idealBodyWeightDesc": "Discover your ideal weight based on height, age, and body structure.", + "bodyFatCalculator": "Body Fat Calculator", + "bodyFatCalculatorDesc": "Estimate your body fat percentage to track fitness and health goals.", + "carbsProteinFat": "Carbs Protein & Fat", + "carbsProteinFatDesc": "Balance your macros with the right carbs, protein, and fat ratios.", + "ovulationPeriod": "Ovulation Period", + "ovulationPeriodDesc": "Track your fertile window and plan for conception with accuracy.", + "deliveryDueDate": "Delivery Due Date", + "deliveryDueDateDesc": "Calculate your expected delivery date and monitor pregnancy milestones.", "low": "Low", "preDiabetic": "Pre-diabetic", "high": "High", @@ -1200,7 +1226,7 @@ "healthTrackers": "Health Trackers", "monitorBloodPressureLevels": "Monitor your blood pressure levels, track systolic and diastolic readings, and maintain a healthy heart.", "trackWeightProgress": "Track your weight progress, set goals, and maintain a healthy body mass for overall wellness.", - "bookAppointment": "Book\nAppointment", + "bookAppointment": "New\nAppointment", "completeCheckup": "Complete Checkup", "indoorNavigation": "Indoor Navigation", "eReferralServices": "E-Referral Services", @@ -1210,10 +1236,11 @@ "healthConvertersServices": "Health\nConverters", "smartWatchesServices": "Smart\nWatches", "exploreServices": "Explore Services", - "medicalAndCareServices": "Medical & Care Services", + "medicalAndCareServices": "Medical & Healthcare Services", "hmgServices": "HMG Services", "personalServices": "Personal Services", - "habibWallet": "Habib Wallet", + "habibWallet": "HMG Wallet", + "habibWallet2": "Habib Wallet", "loginToViewWalletBalance": "Login to view your wallet balance", "loginToViewMedicalFile": "Login to view your medical file", "addMember": "Add Member", @@ -1245,8 +1272,8 @@ "stayHydratedDrinkWater": "Stay hydrated! Drink {amount}ml of water.", "visitPharmacyOnline": "Visit Pharmacy Online", "howAreYouFeelingToday": "How are you feeling today?", - "checkYourSymptomsWithScale": "Check your symptoms with this scale", - "checkYourSymptoms": "Check your symptoms", + "checkYourSymptomsWithScale": "Evaluate your symptoms and find the right care", + "checkYourSymptoms": "Symptom Checker", "noUpcomingAppointmentPleaseBook": "You do not have any upcoming appointment. Please book an appointment", "youHaveEROnlineCheckInRequest": "You have ER Online Check-In Request", "quickLinks": "Quick Links", @@ -1258,9 +1285,9 @@ "requestedServices": "Requested Services", "selectServices": "Select Services", "selectedServices": "Selected Services", - "createNewRequest": "Create new request", - "youHaveNoPendingRequests": "You have no pending requests.", - "noInsuranceDataFound": "No insurance data found...", + "createNewRequest": "New Request", + "youHaveNoPendingRequests": "You have no pending requests", + "noInsuranceinsurance2DataFound": "No insurance data found...", "noInsuranceUpdateRequest": "No insurance update requests found.", "policyNumberInsurancePage": "Policy: {number}", "insuranceExpired": "Insurance Expired", @@ -1278,7 +1305,7 @@ "lastTested": "Last Tested", "byVisit": "By Visit", "byTest": "By Test", - "results": "results", + "results": "Results", "viewResults": "View Results", "rebook": "Rebook", "noOphthalmologyAppointments": "No Ophthalmology appointments found...", @@ -1293,7 +1320,7 @@ "others": "Others", "allergyInfo": "Allergy Info", "vaccineInfo": "Vaccine Info", - "updateInsuranceInfo": "Update Insurance", + "updateInsuranceInfo": "Insurance Update", "myInvoicesList": "My Invoices List", "ancillaryOrdersList": "Ancillary Orders List", "youDontHaveAnySickLeavesYet": "You don't have any sick leaves yet.", @@ -1303,6 +1330,7 @@ "askYourDoctor": "Ask Your Doctor", "internetPairing": "Internet Pairing", "requested": "Requested", + "readyToDownload": "Ready to download", "youDontHaveAnyMedicalReportsYet": "You don't have any medical reports yet.", "requestMedicalReport": "Request medical report", "youDoNotHaveAnyAppointmentsToRequestMedicalReport": "You do not have any appointments to request a medical report.", @@ -1344,7 +1372,7 @@ "downloadPrescription": "Download Prescription", "fetchingPrescriptionDetails": "Fetching prescription details...", "switchBackFamilyFile": "Switch Back To Family File", - "profileAndSettings": "Profile & Settings", + "profileAndSettings": "Settings", "quickActions": "Quick Actions", "notificationsSettings": "Notifications Settings", "touchIDFaceIDServices": "Touch ID / Face ID Services", @@ -1448,6 +1476,8 @@ "setTimerOfReminder": "Set the timer of reminder", "youHaveAppointmentWithDr": "You have appointment with Dr. ", "hours": "Hours", + "minutes": "Minutes", + "seconds": "Seconds", "secs": "Secs", "noAllergiesDataFound": "No allergies data found...", "heartRateDescription": "Your heart rate indicates how many times your heart beats per minute", @@ -1457,6 +1487,7 @@ "distanceDescription": "Distance covered throughout the day", "overview": "Overview", "details": "Details", + "details2": "Details", "healthy": "Healthy", "warning": "Warning", "vitalSignDetails": "Vital Sign Details", @@ -1469,7 +1500,7 @@ "heightDescription": "Height is measured in centimeters and is used to calculate BMI and dosage recommendations.", "weightDescription": "Weight helps track overall health, nutrition, and changes over time.", "bloodPressureDescription": "Blood pressure reflects the force of blood against artery walls. It is shown as systolic/diastolic.", - "temperatureDescription": "Body temperature reflects how hot your body is and may change with infection or inflammation.", + "temperatureDescription": "Body temperature is a vital sign that indicates how warm the body is. It may increase in response to immune triggers like infection or inflammation", "heartRateDescriptionVital": "Heart rate refers to the number of heart beats per minute.", "respiratoryRateDescription": "Respiratory rate is the number of breaths taken per minute.", "bmiAdvice": "Maintain a balanced diet and regular activity. If your BMI is high or low, consider consulting your doctor.", @@ -1509,7 +1540,7 @@ "route": "Route", "frequency": "Frequency", "reminders": "Reminders", - "reminderAddedToCalendar": "Reminder added to calendar ✅", + "reminderAddedToCalendar": "Reminder added to calendar", "errorWhileSettingCalendar": "Error while setting calendar: {error}", "instructions": "Instructions", "requests": "Requests", @@ -1522,18 +1553,18 @@ "pleaseVisitRoomToTheDoctor": "Please visit Room {roomNumber} to the Doctor", "erOnlineCheckInRequest": "ER Online Check-In Request", "indoor": "Indoor", - "navigation": "Navigation", + "thehospital": "Navigation", "health": "Health", "calculators": "Calculators", "converters": "Converters", "guide": "Guide", - "liveCareServiceDesc": "Get medical advice with a specialist doctor Via a video call", + "liveCareServiceDesc": "Get medical advice from a specialist doctor via video call", "bloodGroup": "Blood: {bloodType}", "medicalServices": "Medical Services", "insuranceAndPayments": "Insurance & Payments", "trackerAndOthers": "Tracker & Others", "bmi": "BMI", - "yesPleaseINeedHelp": "Yes, Please I need help", + "yesPleaseINeedHelp": "Yes, Please", "confirmYourLocation": "Confirm Your Location", "confirmYourLocationDesc": "Please confirm your present location to view appropriate directions.", "insideHospital": "I am inside the hospital", @@ -1543,7 +1574,7 @@ "servicePriceListRights": "The patient has the right to a free follow-up within 14 days of initial visit", "serviceName": "Service Name", "noFreeSlot": "No free slot available for the selected date. Please select another date or check back later.", - "noThanksIKnowTheClinic": "No, Thanks. I Know the clinic", + "noThanksIKnowTheClinic": "No, Thanks", "unableToSendOTP": "Unable to send Verification OTP", "aiOverView": "AI Overview", "generateAiAnalysis": "Generate AI analysis for this order", @@ -1551,7 +1582,7 @@ "prediction": "Prediction", "prevention": "Prevention", "disclaimer": "Disclaimer", - "thisAboveInfo": "The above information is generated by AI based on your lab results and is intended for informational purposes only. It should not be considered a medical diagnosis or treatment plan. Always consult with your healthcare provider for personalized medical advice and before making any health-related decisions.", + "thisAboveInfo": "This Lab result was analyzed by AI, and it is not medical advice. Consult your healthcare provider for diagnosis and treatment.", "aiDisclaimer": "Your lab result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?", "consent": "Consent", "bmiNormalDescription": "Your BMI is within the healthy range. Continue a balanced diet, regular physical activity, and routine checkups to maintain good health. Monitor any significant changes over time.", @@ -1613,13 +1644,13 @@ "darkMode": "Dark Mode", "generateAiAnalysisResult": "Generate AI analysis for this result", "ratings": "Ratings", - "hmgPharmacyText": "Al Habib Pharmacy, the complete online Pharmaceutical store brought to you by Dr. Sulaiman Al Habib Medical Services Group.", + "hmgPharmacyText": "Online pharmacy for a complete health shopping experience", "insuranceRequestSubmittedSuccessfully": "Your insurance update request has been successfully submitted. You will be notified once completed.", "updatingEmailAddress": "Updating email address, Please wait...", "verifyInsurance": "Verify Insurance", "tests": "tests", "calendarPermissionAlert": "Please grant calendar access permission from app settings to set medication reminder.", - "timeForFirstReminder": "Time for 1st reminder", + "timeForFirstReminder": "Add Reminder", "reminderRemovalNote": "You can remove it from your calendar later by switching off the reminder", "sortByLocation": "Sort by location", "sortByNearestLocation": "Sort by nearest to your location", @@ -1639,22 +1670,22 @@ "smartWatchServiceDescription": "Seamlessly connect your smartwatch to our health app for effortless data tracking and personalized insights.", "liveChatServiceHeader": "24/7 Expert\nAssistance", "liveChatServiceDescription": "Need help ? Our live chat connects you with expert support for any questions about features, settings, or troubleshooting.", - "emergencyServiceHeader": "ER Check-in, Faster Than Ever. Call ambulance / Rapid Response Team instantly", - "emergencyServiceDescription": "In a medical emergency? Our ambulances and rapid response teams are on standby 24/7. Plus, quick ER check-in for faster care.", - "homeHealthCareServiceHeader": "Your Health, Elevated. Premium care, delivered to your doorstep.", - "homeHealthCareServiceDescription": "We bring quality healthcare to your doorstep. Experienced nurses providing compassionate care in the comfort of your home.", + "emergencyServiceHeader": "Emergency Care, Faster than Ever.", + "emergencyServiceDescription": "Facing a medical emergency? Our ambulances are on standby 24/7 for your immediate assistance.", + "homeHealthCareServiceHeader": "Premium care, delivered right to your doorstep", + "homeHealthCareServiceDescription": "We provide the highest degree of patient care at home in a value-oriented, efficient, and professional manner.", "profileOnlyText": "Profile", "information": "Information", - "noFavouriteDoctors": "You don't have any favourite list yet", + "noFavouriteDoctors": "You don't have any favorite list yet", "addDoctors": "Add Doctors", - "favouriteList": "Favourite List", + "favouriteList": "Favorite List", "later": "Later", "cancelAppointmentConfirmMessage": "Are you sure you want to cancel this appointment?", "acknowledged": "Acknowledged", "searchLabResults": "Search lab results", "callForAssistance": "Call for immediate assistance", "oneWaySubtitle": "Pickup from location to hospital", - "twoWaySubtitle": "Round trip from location to hospital and back", + "twoWaySubtitle": "Round trip from location to hospital", "toHospitalSubtitle": "Transfer from your current location to the hospital", "fromHospitalSubtitle": "Transfer from the hospital back to your home", "clinicSchedule": "Clinic Schedule", @@ -1682,7 +1713,7 @@ "overWeight": "OverWeight", "underWeight": "UnderWeight", "nearestGate": "Nearest Gate", - "RRTDDetails": "The Rapid response team provides a comprehensive medical service for all sorts of urgent and stable cases of all ages, including adult and paediatric patients.", + "RRTDDetails": "Comprehensive medical services for stable and urgent cases, available for all ages.", "fromHospitalSubtitle": "Transfer from the hospital back to your home", "normalMapType": "Default", "satelliteMapType": "Satellite", @@ -1712,5 +1743,48 @@ "aiDisclaimerRad": "Your radiology result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?", "sendingAskDrRequest": "Sending Request...", "askDrRequestSentSuccess": "Request has been sent successfully, The doctor will get back to you soon.", - "onlineCheckIn24HoursOnly": "Online Check-In is only available within 4 hours of the appointment time." + "onlineCheckIn24HoursOnly": "Online Check-In is only available within 4 hours of the appointment time.", + "searchFor": "Search For", + "m": "m", +"inch": "in", + "updateEmailAddressTitle": "Enter the new email address to be updated: ", + "enterNewNewContactNumber": "Enter the new emergency contact number to be updated: ", + "nationalIdFileNumber": "National ID/File Number", + "finda": "Find a", + "calculateCaloriesBasedOnFactors": "Calculates daily calorie intake based on several factors including: height, weight, age, gender and daily physical activity.", + "insurance2": "Insurance", + "conformationCall": "You will receive a call from HMG for confirmation ", + "radiologyLabResults": "Radiology Results", + "disclaimerRadiology": "This radiology result was analyzed by AI, and it is not medical advice. Consult your healthcare provider for diagnosis and treatment.", + "enterIdentificationNumberWithNoStar": "Enter Identification Number", + "enterReferralNumber": "Enter Referral Number", + "referralNumber": "Referral Number", + "phoneHint": "5xxxxxxxx", + "selectSearchCriteria": "Select the Search Criteria", + "selectCriteria": "Select Criteria", + "locationServicesDisabled": "Weather is unavailable because location services are disabled. Please enable location services to see the weather information.", + "selectDateTimeKey": "Select", + "carbsAlert":"Calculate carbohydrate based protein and fat ration in calories and grams according to a pre set ratio.", + "bodyFatAlert": "Estimate the total body fat based on the size.", + "ibwAlert": "Calculates the ideal body weight based on height, weight and body size.", + "glucoseAlert":"Convert blood glucose values between mg/dL and mmol/L. (1 mmol/L ≈ 18.0182 mg/dL)", + "selectBloodGroup": "Select Blood Group", + "routePrescription": "Route", + "noNearestER": "No nearest Er Around you", + "loginToUseServiceLiveCare": "Log In to Use This Service", + "medicalKey": "Medical", + "convertBloodcholesterolInfo": "Convert blood cholesterol values between mg/dL and mmol/L. ", + "mldl": "مليجرام لكل ديسيلتر", + "mmol": "ملي مول لكل لتر", + "triglycerideInfo": "Convert triglyceride values between mg/dL and mmol/L.", + "selectSmartWatch": "Select", + "selectBloodDonationCity": "Select City", + "selectBloodDonationGender": "Select Gender", + "selectBloodType": "Select Blood Type", + "doctors2": "Doctor", + "illurgyInfomation": "Allergy", + "vaccineInfomation": "Vaccine", + "approvals1": "Approvals" } + + diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 374ac9d1..3c71281c 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -172,6 +172,7 @@ class ApiClientImp implements ApiClient { // body['VersionID'] = ApiConsts.appVersionID.toString(); if (!isExternal) { body['VersionID'] = ApiConsts.appVersionID.toString(); + if(!isRCService) body['Channel'] = ApiConsts.appChannelId.toString(); body['IPAdress'] = ApiConsts.appIpAddress; body['generalid'] = ApiConsts.appGeneralId; diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index bf9144fa..ff39fa70 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -189,6 +189,7 @@ class ApiConsts { static final createEReferral = "Services/Patients.svc/REST/CreateEReferral"; static final getEReferrals = "Services/Patients.svc/REST/GetEReferrals"; + //WATER CONSUMPTION static String h2oGetUserProgress = "Services/H2ORemainder.svc/REST/H2O_GetUserProgress"; static String h2oInsertUserActivity = "Services/H2ORemainder.svc/REST/H2O_InsertUserActivity"; @@ -205,6 +206,9 @@ class ApiConsts { static String updateDiabeticResult = 'Services/Patients.svc/REST/Patient_UpdateDiabtecResult'; static String deactivateDiabeticStatus = 'Services/Patients.svc/REST/Patient_DeactivateDiabeticStatus'; static String sendAverageBloodSugarReport = 'Services/Notifications.svc/REST/SendAverageBloodSugarReport'; + static String averageBloodSugarReportDownload = 'Services/Notifications.svc/REST/SendAverageBloodSugarReportDownload'; + static String averageWeightReportDownload = 'Services/Notifications.svc/REST/SendAverageBodyWeightReportDownload'; + static String averageBloodPressureReportDownload = 'Services/Notifications.svc/REST/SendAverageBloodPressureReportDownload'; // Blood Pressure static String getBloodPressureResultAverage = 'Services/Patients.svc/REST/Patient_GetBloodPressureResultAverage'; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 44ca3b10..107e06d1 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -17,6 +17,9 @@ class AppAssets { static const String email = '$svgBasePath/email.svg'; static const String globe = '$svgBasePath/globe.svg'; static const String globeOther = '$svgBasePath/globe_other.svg'; + static const String document = '$svgBasePath/document.svg'; + static const String report = '$svgBasePath/report.svg'; + static const String sucess = '$svgBasePath/sucess.svg'; // static const String globeOther = '$svgBasePath/globe_other.svg'; static const String cancel = '$svgBasePath/cancel.svg'; @@ -119,6 +122,8 @@ class AppAssets { static const String search_by_region_icon = '$svgBasePath/search_by_region_icon.svg'; static const String location_red = '$svgBasePath/location_red.svg'; static const String location_unavailable = '$svgBasePath/location_unavailable.svg'; + static const String location_unavailable_filled = '$svgBasePath/location_unavailable_filled.svg'; + static const String location_unavailable_filled_background = '$svgBasePath/location_unavailable_filled_background.svg'; static const String livecare_clinic_icon = '$svgBasePath/livecare_clinic_icon.svg'; static const String immediate_service_icon = '$svgBasePath/immediate_service_icon.svg'; static const String no_visit_icon = '$svgBasePath/no_visit_icon.svg'; @@ -363,6 +368,14 @@ class AppAssets { static const String files = '$svgBasePath/files.svg'; static const String online_checkin_icon = '$svgBasePath/online_checkin_icon.svg'; + //Health Calculators + static const String bodyfatcalc = '$svgBasePath/bfc.svg'; + static const String duedatecalc = '$svgBasePath/ddc.svg'; + static const String idealbodyweightcacl = '$svgBasePath/ibwc.svg'; + static const String ovluationperiod = '$svgBasePath/op.svg'; + static const String bmicalc = '$svgBasePath/bmi1.svg'; + static const String bmrcalc = '$svgBasePath/bmr1.svg'; + // PNGS // static const String hmgLogo = '$pngBasePath/hmg_logo.png'; static const String liveCareService = '$pngBasePath/livecare_service.png'; @@ -391,7 +404,7 @@ class AppAssets { static const String lockIcon = '$pngBasePath/lock-icon.png'; static const String dummyUser = '$pngBasePath/dummy_user.png'; static const String comprehensiveCheckupEn = '$pngBasePath/cc_en.png'; - static const String comprehensiveCheckupAr = '$pngBasePath/cc_er.png'; + static const String comprehensiveCheckupAr = '$pngBasePath/cc_ar.png'; static const String maleIcon = '$pngBasePath/male_icon.png'; static const String femaleIcon = '$pngBasePath/female_icon.png'; @@ -400,7 +413,13 @@ class AppAssets { static const String fullBodyFrontFemale = '$pngBasePath/full_body_front_female.png'; static const String fullBodyBackFemale = '$pngBasePath/full_body_back_female.png'; static const String bmiFullBody = '$pngBasePath/bmi_image_1.png'; + static const String bmiFullBodyFemale = '$pngBasePath/female_bmi.png'; static const String defaultMEP = '$pngBasePath/default_MEP.png'; + + + + static const String ambulance_section = '$pngBasePath/ambulance_section.png'; + static const String rrt_section = '$pngBasePath/rrt_section.png'; } class AppAnimations { diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 85fe9cb2..21f93eed 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -94,24 +94,33 @@ class AppState { String? get getProfileImageData => _profileImageData; set setProfileImageData(String? value) { + // Clear old data first + if (_profileImageData != null && _profileImageData != value) { + print('🔄 Replacing profile image: old data length=${_profileImageData!.length}, new data length=${value?.length ?? 0}'); + } + + // Set new value directly WITHOUT caching _profileImageData = value; - // Persist to cache + if (value != null && value.isNotEmpty) { - cacheService.saveString(key: _profileImageKey, value: value); + print('✅ Profile image set (length: ${value.length}) - NO CACHE'); } else { - cacheService.remove(key: _profileImageKey); + print('🗑️ Profile image cleared - NO CACHE'); } } - /// Load profile image from cache on app initialization + /// Load profile image from cache on app initialization - DISABLED void _loadProfileImageFromCache() { - _profileImageData = cacheService.getString(key: _profileImageKey); + // DO NOT LOAD FROM CACHE - always fetch fresh from API + _profileImageData = null; + print('📂 Profile image cache disabled - will fetch from API'); } - /// Clear profile image from cache (e.g., on logout) + /// Clear profile image data (e.g., on logout or user switch) void clearProfileImageCache() { + print('🧹 Clearing profile image data (was: ${_profileImageData != null ? "${_profileImageData!.length} chars" : "null"})'); _profileImageData = null; - cacheService.remove(key: _profileImageKey); + print('✅ Profile image data cleared - NO CACHE'); } SelectDeviceByImeiRespModelElement? _selectDeviceByImeiRespModelElement; diff --git a/lib/core/cache_consts.dart b/lib/core/cache_consts.dart index c1e06aa3..c829adbf 100644 --- a/lib/core/cache_consts.dart +++ b/lib/core/cache_consts.dart @@ -78,5 +78,6 @@ class CacheConst { static const String isMonthlyReportEnabled = 'is-monthly-report-enabled'; static const String zoomRoomID = 'zoom-room-id'; + static const String callTypeID = 'call-type-id'; static String isAppOpenedFromCall = "is_app_opened_from_call"; } diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 3b7253db..3b9eefc0 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -64,6 +64,8 @@ import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_repo.dart'; import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart'; +import 'package:hmg_patient_app_new/features/weather/weather_repo.dart'; +import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart'; import 'package:hmg_patient_app_new/features/health_trackers/health_trackers_view_model.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; @@ -74,6 +76,7 @@ import 'package:hmg_patient_app_new/services/localauth_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/services/notification_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'; import 'package:local_auth/local_auth.dart'; import 'package:logger/web.dart'; @@ -131,7 +134,6 @@ class AppDependencies { navigationService: getIt(), )); - final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); getIt.registerLazySingleton(() => NotificationServiceImp( flutterLocalNotificationsPlugin: flutterLocalNotificationsPlugin, @@ -164,13 +166,18 @@ class AppDependencies { getIt.registerLazySingleton(() => SymptomsCheckerRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => BloodDonationRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => WaterMonitorRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => WeatherRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => MyInvoicesRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => HealthTrackersRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => MonthlyReportRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => ActivePrescriptionsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => TermsConditionsRepoImp(loggerService: getIt(), apiClient: getIt())); - getIt.registerFactory(() => TermsConditionsViewModel(termsConditionsRepo: getIt(), errorHandlerService: getIt(), - ),); + getIt.registerFactory( + () => TermsConditionsViewModel( + termsConditionsRepo: getIt(), + errorHandlerService: getIt(), + ), + ); getIt.registerLazySingleton(() => MonthlyReportsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => QrParkingRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => NotificationsRepoImp(loggerService: getIt(), apiClient: getIt())); @@ -230,10 +237,10 @@ class AppDependencies { ); getIt.registerLazySingleton(() => ProfileSettingsViewModel( - cacheService: getIt(), - profileSettingsRepo: getIt(), - errorHandlerService: getIt(), - )); + cacheService: getIt(), + profileSettingsRepo: getIt(), + errorHandlerService: getIt(), + )); getIt.registerLazySingleton(() => DateRangeSelectorRangeViewModel()); @@ -299,6 +306,8 @@ class AppDependencies { getIt.registerLazySingleton(() => WaterMonitorViewModel(waterMonitorRepo: getIt(), errorHandlerService: getIt())); + getIt.registerLazySingleton(() => WeatherMonitorViewModel(weatherRepo: getIt(), errorHandlerService: getIt(), locationUtils: getIt())); + //commenting this because its already define there was on run time error because of this. getIt.registerLazySingleton(() => MyInvoicesViewModel(myInvoicesRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); @@ -307,22 +316,12 @@ class AppDependencies { getIt.registerLazySingleton(() => NotificationsViewModel(notificationsRepo: getIt(), errorHandlerService: getIt())); getIt.registerLazySingleton(() => HealthTrackersViewModel(healthTrackersRepo: getIt(), errorHandlerService: getIt())); - getIt.registerLazySingleton( - () => ActivePrescriptionsViewModel( - errorHandlerService: getIt(), - activePrescriptionsRepo: getIt() - ), - ); - getIt.registerFactory( - () => QrParkingViewModel( - qrParkingRepo: getIt(), - errorHandlerService: getIt(), - cacheService: getIt(), - ), - ); + getIt.registerLazySingleton(() => ActivePrescriptionsViewModel(errorHandlerService: getIt(), activePrescriptionsRepo: getIt())); + getIt.registerFactory(() => QrParkingViewModel(qrParkingRepo: getIt(), errorHandlerService: getIt(), cacheService: getIt())); getIt.registerLazySingleton(() => AskDoctorViewModel(askDoctorRepo: getIt(), errorHandlerService: getIt())); getIt.registerLazySingleton(() => ServicesPriceListViewModel(servicesPriceListRepo: getIt(), errorHandlerService: getIt())); + getIt.registerLazySingleton(() => DateRangCalenderModel(appState: getIt(), navigationService: getIt(), dialogService: getIt())); } } diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 8bb8330c..4ac919e7 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -5,8 +5,11 @@ // unverified, // } +import 'package:easy_localization/easy_localization.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/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; enum AuthMethodTypesEnum { sms, whatsApp, fingerPrint, faceID, moreOptions } @@ -20,6 +23,17 @@ enum SelectionTypeEnum { dropdown, calendar, search, time } enum GenderTypeEnum { male, female } +extension GenderTypeEnumExtension on GenderTypeEnum { + String get displayName { + switch (this) { + case GenderTypeEnum.male: + return LocaleKeys.malE.tr(); + case GenderTypeEnum.female: + return LocaleKeys.femaleGender.tr(); + } + } +} + enum MaritalStatusTypeEnum { single, married, divorced, widowed } enum ChipTypeEnum { success, error, alert, info, warning, lightBg, primaryRed } @@ -58,17 +72,17 @@ extension HealthCalculatorExtenshion on HealthCalculatorsTypeEnum { bool isArabic = appState.getLanguageID() == 1 ? true : false; switch (this) { case HealthCalculatorsTypeEnum.bmi: - return isArabic ? "حاسبة مؤشر كتلة الجسم" : "BMI Calculator"; + return isArabic ? "مؤشر كتلة الجسم" : "BMI Calculator"; case HealthCalculatorsTypeEnum.calories: - return isArabic ? "حاسبة السعرات الحرارية" : "Calories Calculator"; + return isArabic ? "السعرات الحرارية" : "Calories Calculator"; case HealthCalculatorsTypeEnum.bmr: - return isArabic ? "حاسبة معدل الأيض الأساسي" : "BMR Calculator"; + return isArabic ? "معدل الايض" : "BMR Calculator"; case HealthCalculatorsTypeEnum.idealBodyWeight: - return isArabic ? "الوزن المثالي للجسم" : "Ideal Body Weight Calculator"; + return isArabic ? "الوزن المثالي" : "Ideal Body Weight Calculator"; case HealthCalculatorsTypeEnum.bodyFat: - return isArabic ? "حاسبة الدهون في الجسم" : "Body Fat Calculator"; + return isArabic ? "دهون الجسم" : "Body Fat Calculator"; case HealthCalculatorsTypeEnum.crabsProteinFat: - return isArabic ? "حاسبة البروتين والدهون في سرطان البحر" : "Crabs Protein & Fat Calculator"; + return isArabic ? "حاسبة المغذيات" : "Crabs Protein & Fat Calculator"; case HealthCalculatorsTypeEnum.ovulation: return isArabic ? "فترة الإباضة" : "Ovulation Period"; case HealthCalculatorsTypeEnum.deliveryDueDate: @@ -80,6 +94,22 @@ extension HealthCalculatorExtenshion on HealthCalculatorsTypeEnum { case HealthCalculatorsTypeEnum.triglycerides: return isArabic ? "الدهون الثلاثية في الدم" : "Triglycerides Fat Blood"; } + } + + String get title { + AppState appState = getIt.get(); + bool isArabic = appState.getLanguageID() == 1 ? true : false; + switch (this) { + case HealthCalculatorsTypeEnum.crabsProteinFat: + return isArabic ? "نتيجة المغذيات" : "Nutrient Results"; + case HealthCalculatorsTypeEnum.calories: + return isArabic ? "سعراتك الحرارية" : "Calories"; + + case HealthCalculatorsTypeEnum.calories: + return isArabic ? "مؤشر كتلة الجسم" : "BMI"; + default: + return "Your ${this.name.toCamelCase}"; + } } static LoginTypeEnum? fromValue(int value) { diff --git a/lib/core/location_util.dart b/lib/core/location_util.dart index c56c63d4..52a2e93d 100644 --- a/lib/core/location_util.dart +++ b/lib/core/location_util.dart @@ -89,7 +89,7 @@ class LocationUtils { } } - LocationPermission permissionGranted = await Geolocator.checkPermission(); + LocationPermission permissionGranted = await Geolocator.checkPermission(); if (permissionGranted == LocationPermission.denied) { permissionGranted = await Geolocator.requestPermission(); if (permissionGranted != LocationPermission.whileInUse && permissionGranted != LocationPermission.always) { @@ -257,6 +257,30 @@ class LocationUtils { appState.userLong = locationData.longitude; } + Future isLocationPermissionEnabled() async { + if (Platform.isIOS || (await isGMSDevice ?? true)) { + return await _isGMSLocationPermissionEnabled(); + } else { + return await _isHMSLocationPermissionEnabled(); + } + } + + Future _isGMSLocationPermissionEnabled() async { + bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) return false; + + LocationPermission permission = await Geolocator.checkPermission(); + return permission == LocationPermission.whileInUse || permission == LocationPermission.always; + } + + Future _isHMSLocationPermissionEnabled() async { + bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) return false; + + LocationPermission permission = await Geolocator.checkPermission(); + return permission == LocationPermission.whileInUse || permission == LocationPermission.always; + } + void getHMSLocation({VoidCallback? onFailure, Function(LatLng p1)? onSuccess, VoidCallback? onLocationDeniedForever}) async { try { var location = Location(); diff --git a/lib/core/utils/calender_utils_new.dart b/lib/core/utils/calender_utils_new.dart index 6f043e26..bbf69119 100644 --- a/lib/core/utils/calender_utils_new.dart +++ b/lib/core/utils/calender_utils_new.dart @@ -16,33 +16,64 @@ class CalenderUtilsNew { final DeviceCalendar calender = DeviceCalendar.instance; List writableCalender = []; - CalenderUtilsNew._instance() { - getCalenders(); - } + /// Completer to ensure getCalenders() only runs once at a time + Completer? _calendarInitCompleter; + + CalenderUtilsNew._instance(); static final CalenderUtilsNew instance = CalenderUtilsNew._instance(); - Future getCalenders() async { + /// Ensures calendars are loaded exactly once. Safe to call multiple times — + /// subsequent calls await the same future. + Future ensureCalendarsLoaded() async { + if (writableCalender.isNotEmpty) return; + if (_calendarInitCompleter != null) { + // Already in progress — just wait for it + return _calendarInitCompleter!.future; + } + _calendarInitCompleter = Completer(); + try { + await _loadCalendars(); + _calendarInitCompleter!.complete(); + } catch (e) { + _calendarInitCompleter!.completeError(e); + _calendarInitCompleter = null; // Allow retry on failure + rethrow; + } + } + + Future _loadCalendars() async { + print("the get calenders is called"); CalendarPermissionStatus result = await DeviceCalendar.instance.hasPermissions(); if (result != CalendarPermissionStatus.granted) { - // await DeviceCalendar.instance.requestPermissions(); - showCommonBottomSheetWithoutHeight( - title: LocaleKeys.notice.tr(context: GetIt.instance().navigatorKey.currentContext!), - GetIt.instance().navigatorKey.currentContext!, - child: Utils.getWarningWidget( - loadingText: LocaleKeys.calendarPermissionAlert.tr(), - isShowActionButtons: true, - onCancelTap: () { - GetIt.instance().pop(); - }, - onConfirmTap: () async { - GetIt.instance().pop(); - openAppSettings(); - }), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); + await DeviceCalendar.instance.requestPermissions(); + result = await DeviceCalendar.instance.hasPermissions(); + // if (result != CalendarPermissionStatus.granted) { + // print("the permission is not granted"); + // showCommonBottomSheetWithoutHeight( + // title: LocaleKeys.notice.tr(context: GetIt + // .instance() + // .navigatorKey + // .currentContext!), + // GetIt + // .instance() + // .navigatorKey + // .currentContext!, + // child: Utils.getWarningWidget( + // loadingText: LocaleKeys.calendarPermissionAlert.tr(), + // isShowActionButtons: true, + // onCancelTap: () { + // GetIt.instance().pop(); + // }, + // onConfirmTap: () async { + // GetIt.instance().pop(); + // openAppSettings(); + // }), + // callBackFunc: () {}, + // isFullScreen: false, + // isCloseButtonVisible: true, + // ); + // } } var calenders = await calender.listCalendars(); calenders.forEach((calender) { @@ -52,21 +83,13 @@ class CalenderUtilsNew { }); } - FutureOr createOrUpdateEvent( - {required String title, - required String description, - required String location, - DateTime? scheduleDateTime, - String? eventId, - int? reminderMinutes}) async { - if (writableCalender.isEmpty) { - await getCalenders(); - } + FutureOr createOrUpdateEvent({required String title, required String description, required String location, DateTime? scheduleDateTime, String? eventId, int? reminderMinutes}) async { + await ensureCalendarsLoaded(); var writableCalendars = writableCalender.first; String eventResult = ""; - CalendarPermissionStatus result = await DeviceCalendar.instance.hasPermissions(); - if (result != CalendarPermissionStatus.granted) await DeviceCalendar.instance.requestPermissions(); - print(result); + // CalendarPermissionStatus result = await DeviceCalendar.instance.hasPermissions(); + // if (result != CalendarPermissionStatus.granted) await DeviceCalendar.instance.requestPermissions(); + // print(result); // String eventId = await getEventIdIfEventExist(title!.split("#").last); // if (eventId.isEmpty) { @@ -93,19 +116,19 @@ class CalenderUtilsNew { // return eventId.isNotEmpty; } - FutureOr createMultipleEvents( - {required int reminderMinutes, - int? frequencyNumber, - required int days, - required String orderDate, - required String itemDescriptionN, - required String route, - Function(String)? onFailure, - String? prescriptionNumber, - DateTime? scheduleDateTime, - }) async { + FutureOr createMultipleEvents({ + required int reminderMinutes, + int? frequencyNumber, + required int days, + required String orderDate, + required String itemDescriptionN, + required String route, + Function(String)? onFailure, + String? prescriptionNumber, + DateTime? scheduleDateTime, + }) async { DateTime currentDay = DateTime.now(); - DateTime actualDate = scheduleDateTime??DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, 8, 0); + DateTime actualDate = scheduleDateTime ?? DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, 8, 0); print("the frequency is $frequencyNumber"); frequencyNumber ??= 2; //Some time frequency number is null so by default will be 2 int interval = calculateIntervalAsPerFrequency(frequencyNumber); @@ -152,9 +175,7 @@ class CalenderUtilsNew { } FutureOr checkIfEventExist(String admissionId) async { - if (writableCalender.isEmpty) { - await getCalenders(); - } + await ensureCalendarsLoaded(); List events = await getEvents(); if (events.isEmpty) return false; for (var event in events) { @@ -167,7 +188,7 @@ class CalenderUtilsNew { FutureOr getEventIdIfEventExist(String admissionId) async { if (writableCalender.isEmpty) { - await getCalenders(); + await ensureCalendarsLoaded(); } List events = await getEvents(); if (events.isEmpty) return ""; @@ -180,7 +201,7 @@ class CalenderUtilsNew { FutureOr checkAndRemove({required String id}) async { if (writableCalender.isEmpty) { - await getCalenders(); + await ensureCalendarsLoaded(); } List events = await getEvents(); if (events.isEmpty) return false; @@ -197,7 +218,7 @@ class CalenderUtilsNew { FutureOr checkAndRemoveMultipleItems({required String id}) async { if (writableCalender.isEmpty) { - await getCalenders(); + await ensureCalendarsLoaded(); } List events = await getEvents(); if (events.isEmpty) return false; diff --git a/lib/core/utils/date_util.dart b/lib/core/utils/date_util.dart index 6c4e7bec..5aa48b07 100644 --- a/lib/core/utils/date_util.dart +++ b/lib/core/utils/date_util.dart @@ -599,6 +599,17 @@ class DateUtil { return '${hours} hr ${minutes} min'; } + + /// Parses an ISO 8601 date string that may have more than 6 fractional-second digits. + /// e.g. '2026-04-12T12:07:35.4226413' → truncates to 6 digits before parsing. + static DateTime? parseISODate(String? date) { + if (date == null || date.isEmpty) return null; + final truncated = date.replaceFirstMapped( + RegExp(r'(\.\d{6})\d+'), + (m) => m.group(1)!, + ); + return DateTime.tryParse(truncated); + } } extension OnlyDate on DateTime { diff --git a/lib/core/utils/push_notification_handler.dart b/lib/core/utils/push_notification_handler.dart index e2925c29..c82c608d 100644 --- a/lib/core/utils/push_notification_handler.dart +++ b/lib/core/utils/push_notification_handler.dart @@ -9,17 +9,27 @@ import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_callkit_incoming/entities/android_params.dart'; +import 'package:flutter_callkit_incoming/entities/call_event.dart'; import 'package:flutter_callkit_incoming/entities/call_kit_params.dart'; import 'package:flutter_callkit_incoming/entities/ios_params.dart'; import 'package:flutter_callkit_incoming/entities/notification_params.dart'; import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; import 'package:flutter_ios_voip_kit_karmm/call_state_type.dart'; import 'package:flutter_ios_voip_kit_karmm/flutter_ios_voip_kit.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/utils/utils.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:uuid/uuid.dart'; +// Global tracking variables for call state +bool _isCallInProgress = false; +String? _currentCallId; +Timer? _callTimeoutTimer; +String? _currentSessionId; // Store session ID for API call + // |--> Push Notification Background @pragma('vm:entry-point') Future backgroundMessageHandler(dynamic message) async { @@ -39,30 +49,37 @@ callPage(String sessionID, String token) async {} _incomingCall(Map data) async { print('the value of the _incomingCall remote message is $data'); - // LandingPage.incomingCallData = IncomingCallData.fromJson(data); - // var dataItem = await AppSharedPreferences().getObject('call_data'); - // if(dataItem != null ) return; // to stop repeated attempt to invoke the call - // if (LandingPage.isOpenCallPage == false) { - // LandingPage.isOpenCallPage = true; + // Check if there's already a call in progress to prevent duplicates + if (_isCallInProgress && _currentCallId != null) { + print('⚠️ Call already in progress (ID: $_currentCallId), ignoring duplicate notification'); + return; + } String roomID = data['session_id'] ?? ''; + String callTypeID = data['AppointmentNo'] ?? ''; - // GetIt.instance().saveString(key: CacheConst.zoomRoomID, value: roomID); - // GetIt.instance().saveBool(key: CacheConst.isAppOpenedFromCall, value: true); + // Generate unique call ID + var _currentUuid = Uuid().v4(); + _currentCallId = _currentUuid; + _isCallInProgress = true; + _currentSessionId = roomID; // Store session ID for decline API call + + print('📞 Starting new call with ID: $_currentCallId, Session: $_currentSessionId'); - Utils.saveStringFromPrefs(CacheConst.zoomRoomID, roomID); - Utils.saveBoolFromPrefs(CacheConst.isAppOpenedFromCall, true); + await Utils.saveStringFromPrefs(CacheConst.zoomRoomID, roomID); + await Utils.saveStringFromPrefs(CacheConst.callTypeID, callTypeID); + await Utils.saveBoolFromPrefs(CacheConst.isAppOpenedFromCall, true); WidgetsFlutterBinding.ensureInitialized(); - var _currentUuid = Uuid().v4(); + int callKitType = callTypeID == '2' ? 0 : 1; + CallKitParams callKitParams = CallKitParams( id: _currentUuid, nameCaller: "Dr Al Habib", appName: 'Dr Al Habib', avatar: 'https://play-lh.googleusercontent.com/FBNNpxb7m6eM6wtW7MV1Ffp6OXOGLI38q47zcvP29OCYA1yhYH5mZzl5itZi0TgOyZpG', - // handle: LandingPage.incomingCallData.name, - type: 1, + type: callKitType, textAccept: 'Accept', textDecline: 'Decline', missedCallNotification: NotificationParams( @@ -78,7 +95,11 @@ _incomingCall(Map data) async { callbackText: 'Hang Up', ), duration: 30000, - extra: {'userId': '1a2b3c4d'}, + extra: { + 'userId': '1a2b3c4d', + 'callTypeID': callTypeID, + 'roomID': roomID, + }, headers: {'apiKey': 'Abc@123!', 'platform': 'flutter'}, android: const AndroidParams( isImportant: true, @@ -87,10 +108,10 @@ _incomingCall(Map data) async { isShowLogo: false, logoUrl: "https://play-lh.googleusercontent.com/FBNNpxb7m6eM6wtW7MV1Ffp6OXOGLI38q47zcvP29OCYA1yhYH5mZzl5itZi0TgOyZpG", ringtonePath: 'system_ringtone_default', - backgroundColor: '#ED1C2B', + backgroundColor: '#2E3039', backgroundUrl: 'https://saudipedia.com/en/saudipediaen/uploads/images/2024/08/02/97358.jpg', actionColor: '#4CAF50', - textColor: '#000000', + textColor: '#FFFFFF', incomingCallNotificationChannelName: "Incoming Call", missedCallNotificationChannelName: "Missed Call", isShowCallID: false), @@ -111,12 +132,89 @@ _incomingCall(Map data) async { ringtonePath: 'system_ringtone_default', ), ); + FlutterCallkitIncoming.unsilenceEvents(); await FlutterCallkitIncoming.showCallkitIncoming(callKitParams); + // Start 60-second timeout timer to auto-dismiss call + _callTimeoutTimer?.cancel(); // Cancel any existing timer + _callTimeoutTimer = Timer(Duration(seconds: 40), () async { + print('⏱️ Call timeout (60 seconds) - auto dismissing call notification'); + await _endCallAndCleanup(_currentUuid); + }); + await Future.delayed(Duration(milliseconds: 500)); } +// Helper method to end call and cleanup +Future _endCallAndCleanup(String callId, {bool isDeclined = false}) async { + try { + print('🧹 Cleaning up call: $callId'); + + // If call was declined, update session status via API + if (isDeclined && _currentSessionId != null && _currentSessionId!.isNotEmpty) { + await _updateSessionStatus(_currentSessionId!, 3, 'Patient'); + } + + // End the specific call + await FlutterCallkitIncoming.endCall(callId); + + // Clear all calls to ensure clean state + await FlutterCallkitIncoming.endAllCalls(); + + // Reset tracking flags + _isCallInProgress = false; + _currentCallId = null; + _currentSessionId = null; + + // Cancel timer + _callTimeoutTimer?.cancel(); + _callTimeoutTimer = null; + + // Clear cache + await Utils.saveBoolFromPrefs(CacheConst.isAppOpenedFromCall, false); + await Utils.saveStringFromPrefs(CacheConst.zoomRoomID, ''); + await Utils.saveStringFromPrefs(CacheConst.callTypeID, ''); + + print('✅ Call and push notification cleanup completed successfully'); + } catch (e) { + print('❌ Error during call cleanup: $e'); + // Force reset flags even if there's an error + _isCallInProgress = false; + _currentCallId = null; + _currentSessionId = null; + _callTimeoutTimer?.cancel(); + _callTimeoutTimer = null; + } +} + +// Helper method to update session status via API +Future _updateSessionStatus(String sessionId, int sessionStatus, String sessionEndedBy) async { + try { + + final apiClient = getIt.get(); + + Map requestBody = { + "Open_SessionID": sessionId, + "SessionStatus": sessionStatus, + "SessionEndedBy": sessionEndedBy + }; + + await apiClient.post( + CHANGE_PATIENT_ER_SESSION, + body: requestBody, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + print('✅ Session status updated successfully: $response'); + }, + onFailure: (error, statusCode, {messageStatus, failureType}) { + print('❌ Failed to update session status: $error'); + }, + ); + } catch (e) { + print('❌ Exception updating session status: $e'); + } +} + Future openCallPage(BuildContext context) async { try { // if (incomingCallData!.background == "0") { @@ -282,6 +380,10 @@ class PushNotificationHandler { try { print('🎈 example: onDidRejectIncomingCall $uuid - $callerId'); timeOutTimer.cancel(); + // Cleanup on reject with API call + if (_currentCallId != null) { + await _endCallAndCleanup(_currentCallId!, isDeclined: true); + } } catch (err) {} }; @@ -292,6 +394,12 @@ class PushNotificationHandler { print('🎈 example: onDidAcceptIncomingCall $uuid - $callerId'); await voIPKit.acceptIncomingCall(callerState: CallStateType.calling); await voIPKit.callConnected(); + + // Cleanup on accept - cancel timer and reset flags + _callTimeoutTimer?.cancel(); + _callTimeoutTimer = null; + _isCallInProgress = false; + _currentCallId = null; await Future.delayed(Duration(seconds: 2)).then((val) async { await voIPKit.endCall(); }); diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index bb947398..4d4ae4b5 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -1,7 +1,6 @@ import 'dart:developer'; import 'package:easy_localization/easy_localization.dart'; -import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; @@ -12,6 +11,7 @@ import 'package:hmg_patient_app_new/features/authentication/models/request_model import 'package:hmg_patient_app_new/features/authentication/models/request_models/send_activation_request_model.dart'; import 'package:hmg_patient_app_new/features/common/models/commong_authanticated_req_model.dart'; import 'package:hmg_patient_app_new/features/common/models/family_file_request.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart'; class RequestUtils { static dynamic getPatientAuthenticationRequest({ diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index bc4b82c0..615641b5 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -820,6 +820,28 @@ class Utils { // return Image.asset(icon, width: width, height: height, fit: fit, ); } + static Widget buildImgWithAssetsWithBorderRadius({ + required String icon, + Color? iconColor, + bool isDisabled = false, + double? width, + double? height, + BoxFit fit = BoxFit.cover, + double? border, + double? borderRadius, + }) { + final iconH = height ?? 24.h; + final iconW = width ?? 24.w; + return Container( + width: iconW, height: iconH, + decoration: BoxDecoration( + border: border != null ? Border.all(color: AppColors.whiteColor, width: border) : null, + borderRadius: borderRadius != null ? BorderRadius.circular(borderRadius ?? 12.r) : null, + image: DecorationImage(image: AssetImage(icon,), fit: fit) + ), + ); + } + /// Widget to build an SVG from network static Widget buildImgWithNetwork({ required String url, diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 5db5b289..7a71b345 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -322,8 +322,10 @@ extension EmailValidator on String { Widget toText32({bool isEnglishOnly = false, FontWeight? weight, Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, + style: TextStyle( - height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.f, letterSpacing: -1, fontFamily: getIt.get().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins', fontWeight: isBold ? FontWeight.bold : weight ?? FontWeight.normal), + + height: getIt.get().getLanguageCode() == "ar" ? 1.2 : 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.f, letterSpacing: -1, fontFamily: getIt.get().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins', fontWeight: isBold ? FontWeight.bold : weight ?? FontWeight.normal,), ); Widget toText44({Color? color, bool isBold = false, bool isEnglishOnly = false,}) => Text( diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 45c2d620..eb922652 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -6,7 +6,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:get_it/get_it.dart'; -import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.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'; @@ -42,6 +41,7 @@ import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/localauth_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.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:sms_otp_auto_verify/sms_otp_auto_verify.dart'; @@ -152,10 +152,12 @@ class AuthenticationViewModel extends ChangeNotifier { void onDobChange(String? date) { if (calenderType == CalenderEnum.hijri) { - var hijriDate = HijriGregConverter.gregorianToHijri(DateTime.parse(date!)); + // Use DateRangCalenderModel for accurate Umm al-Qura conversion + final calendarModel = DateRangCalenderModel(appState: _appState, navigationService: _errorHandlerService, dialogService: _dialogService); + var hijriDate = calendarModel.gregorianToHijri(DateTime.parse(date!)); DateTime hijriDateTimeForController = DateTime(hijriDate.year, hijriDate.month, hijriDate.day); dob = Utils.formatDateForApi(date); - dobController.text = Utils.formatHijriDateToDisplay(hijriDateTimeForController.toIso8601String()); // Or directly hijriDate.toString() if that's what your formatter expects + dobController.text = Utils.formatHijriDateToDisplay(hijriDateTimeForController.toIso8601String()); } else { dobController.text = Utils.formatDateToDisplay(date!); dob = Utils.formatDateForApi(date); @@ -897,7 +899,13 @@ class AuthenticationViewModel extends ChangeNotifier { } final resultEither = await _authenticationRepo.checkPatientForRegistration(commonAuthanticatedRequest: nRequest); - resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { + resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure, onUnHandledFailure: (failur) { + + LoaderBottomSheet.hideLoader(); + _dialogService.showExceptionBottomSheet(message: failur.message, onOkPressed: () { + _navigationService.pop(); + }); + }), (apiResponse) async { checkUserStatusForRegistration(response: apiResponse.data, request: request); }); } @@ -907,6 +915,7 @@ class AuthenticationViewModel extends ChangeNotifier { // LoadingUtils.showFullScreenLoader(loadingText: "Setting up your medical file.\nMay take a moment."); var request = RequestUtils.getUserSignupCompletionRequest(fullName: nameController.text, emailAddress: emailController.text, gender: genderType, maritalStatus: maritalStatus); + final resultEither = await _authenticationRepo.registerUser(registrationPayloadDataModelRequest: request); resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { if (apiResponse.data is String) { @@ -926,6 +935,8 @@ class AuthenticationViewModel extends ChangeNotifier { _navigationService.pushAndRemoveUntil(CustomPageRoute(page: LandingNavigation()), (r) => false); _navigationService.push(CustomPageRoute(page: LoginScreen())); }); + } else { + print("========="); } } }); @@ -1109,7 +1120,7 @@ class AuthenticationViewModel extends ChangeNotifier { _dialogService.showPhoneNumberPickerSheet( // label: "Where would you like to receive OTP?", label: LocaleKeys.receiveOtpToast.tr(), - message: "Please select from the below options to receive OTP.", + message: _appState.isArabic() ? "يرجى الاختيار من الخيارات أدناه لاستلام رمز التحقق." "Please select from the below options to receive OTP.", onSMSPress: () { checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms); }, @@ -1240,6 +1251,12 @@ class AuthenticationViewModel extends ChangeNotifier { try { log("Fetching profile image for patient ID: $patientID"); + + // IMPORTANT: Clear old cache BEFORE fetching new image + // This ensures old data is removed immediately + _appState.clearProfileImageCache(); + log("🧹 Cleared profile image cache before fetch"); + final profileSettingsRepo = GetIt.instance(); final result = await profileSettingsRepo.getProfileImage(patientID: patientID); @@ -1247,6 +1264,8 @@ class AuthenticationViewModel extends ChangeNotifier { (failure) { // Silently fail - profile image is optional log("Failed to fetch profile image: ${failure.message}"); + // Ensure cache is cleared even on failure + _appState.setProfileImageData = null; }, (apiResponse) { if (apiResponse.data != null && apiResponse.data['Patient_GetProfileImageDataList'] != null) { @@ -1254,21 +1273,26 @@ class AuthenticationViewModel extends ChangeNotifier { if (imageList is List && imageList.isNotEmpty) { String? imageData = imageList[0]['ImageData']; if (imageData != null && imageData.isNotEmpty) { + // Set new profile image data - this will update cache and notify all listeners _appState.setProfileImageData = imageData; - log("✅ Profile image loaded and cached successfully"); + log("✅ Profile image loaded and cached successfully for patient: $patientID"); } else { - log("⚠️ Profile image data is empty"); + log("⚠️ Profile image data is empty for patient: $patientID"); + _appState.setProfileImageData = null; } } else { - log("⚠️ Profile image list is empty"); + log("⚠️ Profile image list is empty for patient: $patientID"); + _appState.setProfileImageData = null; } } else { - log("⚠️ No profile image data in response"); + log("⚠️ No profile image data in response for patient: $patientID"); + _appState.setProfileImageData = null; } }, ); } catch (e) { log("❌ Error fetching profile image: $e"); + _appState.setProfileImageData = null; } } } diff --git a/lib/features/authentication/widgets/otp_verification_screen.dart b/lib/features/authentication/widgets/otp_verification_screen.dart index 9e80dbb9..b3b5e3eb 100644 --- a/lib/features/authentication/widgets/otp_verification_screen.dart +++ b/lib/features/authentication/widgets/otp_verification_screen.dart @@ -558,9 +558,9 @@ class _OTPVerificationScreenState extends State { LocaleKeys.via.tr(context: context).toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4), (widget.isFormFamilyFile ? LoginTypeEnum.sms.displayName : authVM.loginTypeEnum.displayName) .toText15(color: AppColors.inputLabelTextColor, isBold: true, letterSpacing: -0.4), - appState.getUserRegistrationPayload.isRegister != null && appState.getUserRegistrationPayload.isRegister == true - ? LocaleKeys.forRegistrationVerification.tr(context: context).toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4) - : LocaleKeys.forLoginVerification.tr(context: context).toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4), + // appState.getUserRegistrationPayload.isRegister != null && appState.getUserRegistrationPayload.isRegister == true + // ? LocaleKeys.forRegistrationVerification.tr(context: context).toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4) + // : LocaleKeys.forLoginVerification.tr(context: context).toText15(color: AppColors.inputLabelTextColor, letterSpacing: -0.4), ], ), diff --git a/lib/features/emergency_services/emergency_services_repo.dart b/lib/features/emergency_services/emergency_services_repo.dart index 10c5541a..582c4a58 100644 --- a/lib/features/emergency_services/emergency_services_repo.dart +++ b/lib/features/emergency_services/emergency_services_repo.dart @@ -183,7 +183,7 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { @override Future>>> getProjectList() async { - Map request = {'IsOnlineCheckIn': true, 'IsAdvancePayment': false, 'IsForER': true}; + Map request = {"IsEROnlineCheckIn":true,'IsOnlineCheckIn': false, 'IsAdvancePayment': false, 'IsForER': true}; try { GenericApiModel>? apiResponse; diff --git a/lib/features/emergency_services/emergency_services_view_model.dart b/lib/features/emergency_services/emergency_services_view_model.dart index c8a16455..83d2a564 100644 --- a/lib/features/emergency_services/emergency_services_view_model.dart +++ b/lib/features/emergency_services/emergency_services_view_model.dart @@ -54,6 +54,7 @@ import 'package:hmg_patient_app_new/widgets/map/map_utility_screen.dart'; import 'package:hmg_patient_app_new/widgets/order_tracking/order_tracking_state.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:huawei_map/huawei_map.dart' as HMSCameraServices; +import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; import '../location/GeocodeResponse.dart'; @@ -274,7 +275,6 @@ class EmergencyServicesViewModel extends ChangeNotifier { void navigateTOAmbulancePage() { placeValueInController(); - getProjects(); flushData(); selectedFacility = FacilitySelection.ALL; @@ -282,6 +282,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { locationUtils!.getLocation( isShowConfirmDialog: true, onSuccess: (position) { + getProjects(); updateBottomSheetState(BottomSheetType.FIXED); navServices.push( CustomPageRoute(page: CallAmbulancePage(), direction: AxisDirection.down), @@ -310,17 +311,21 @@ class EmergencyServicesViewModel extends ChangeNotifier { } void updateBottomSheetState(BottomSheetType sheetType) { - if (sheetType == BottomSheetType.EXPANDED && selectedHospital == null) { - dialogService.showErrorBottomSheet(message: LocaleKeys.selectHospital.tr()); - return; - } + // if (sheetType == BottomSheetType.EXPANDED && selectedHospital == null) { + // dialogService.showErrorBottomSheet(message: LocaleKeys.selectHospital.tr()); + // return; + // } bottomSheetType = sheetType; - if (sheetType == BottomSheetType.EXPANDED) { - clearAppointmentData(); - } + // if (sheetType == BottomSheetType.FIXED) { + // clearAppointmentData(); + // } notifyListeners(); } + void showError(String message) { + dialogService.showErrorBottomSheet(message: message); + } + void setIsGMSAvailable(bool value) { notifyListeners(); } @@ -732,6 +737,10 @@ class EmergencyServicesViewModel extends ChangeNotifier { } FutureOr submitAmbulanceRequest(GeocodeResult? result, PlaceDetails? place, PlacePrediction? placePrediction) async { + if (false == haveAnAppointment && null == selectedHospital) { + dialogService.showErrorBottomSheet(message: LocaleKeys.selectHospital.tr()); + return; + } PatientER_RC request = createRequest(result, place, placePrediction); navServices.push(CustomPageRoute(page: RequestingServicesPage())); @@ -802,19 +811,19 @@ class EmergencyServicesViewModel extends ChangeNotifier { request.patientERTransportationMethod = selectedTransportOption; transportationDetails.pickupUrgency = 1; switch (callingPlace) { - case AmbulanceCallingPlace.FROM_HOSPITAL: - transportationDetails.dropoffLatitude = selectedHospital!.latitude; - transportationDetails.dropoffLongitude = selectedHospital!.longitude; - transportationDetails.dropoffLocationName = selectedHospital!.name; + case AmbulanceCallingPlace.TO_HOSPITAL: + transportationDetails.dropoffLatitude = selectedHospital?.latitude; + transportationDetails.dropoffLongitude = selectedHospital?.longitude; + transportationDetails.dropoffLocationName = selectedHospital?.name; transportationDetails.pickupLatitude = ((result?.geometry.location.lat) ?? place?.lat)?.toStringAsFixed(6); transportationDetails.pickupLongitude = ((result?.geometry.location.lng) ?? place?.lng)?.toStringAsFixed(6); transportationDetails.pickupLocationName = result?.formattedAddress ?? placePrediction?.description; transportationDetails.direction = 1; break; - case AmbulanceCallingPlace.TO_HOSPITAL: - transportationDetails.pickupLatitude = selectedHospital!.latitude; - transportationDetails.pickupLongitude = selectedHospital!.longitude; - transportationDetails.pickupLocationName = selectedHospital!.name; + case AmbulanceCallingPlace.FROM_HOSPITAL: + transportationDetails.pickupLatitude = selectedHospital?.latitude; + transportationDetails.pickupLongitude = selectedHospital?.longitude; + transportationDetails.pickupLocationName = selectedHospital?.name; transportationDetails.dropoffLatitude = ((result?.geometry.location.lat) ?? place?.lat)?.toStringAsFixed(6); transportationDetails.dropoffLongitude = ((result?.geometry.location.lng) ?? place?.lng)?.toStringAsFixed(6); transportationDetails.dropoffLocationName = result?.formattedAddress ?? placePrediction?.description; @@ -986,19 +995,47 @@ class EmergencyServicesViewModel extends ChangeNotifier { this.currentlyDisplayedOrder = currentlyDisplayedOrder; switch (currentlyDisplayedOrder) { case OrderDislpay.ALL: - orderDisplayList = allOrders; + orderDisplayList = List.from(allOrders); break; case OrderDislpay.RRT: - orderDisplayList = ordersRRT?.completedOrders ?? []; + orderDisplayList = List.from(ordersRRT?.completedOrders ?? []); break; case OrderDislpay.AMBULANCE: - orderDisplayList = ambulanceOrders ?? []; + orderDisplayList = List.from(ambulanceOrders ?? []); break; } + + orderDisplayList = _sortOrdersByDate(orderDisplayList); notifyListeners(); } - void openRRT() { + List _sortOrdersByDate(List list) { + list.sort((a, b) { + final int? dateA = _parseCreatedDate(a)?.millisecondsSinceEpoch; + final int? dateB = _parseCreatedDate(b)?.millisecondsSinceEpoch; + print("the dateA is $dateA and dateB is $dateB"); + if (dateA == null && dateB == null) return 0; + if (dateA == null) return 1; + if (dateB == null) return -1; + return dateB.compareTo(dateA); + }); + return list; + } + + DateTime? _parseCreatedDate(dynamic order) { + final String? raw = _getCreatedDate(order); + + + return DateTime.tryParse(raw??""); + } + + String? _getCreatedDate(dynamic order) { + if (order is AmbulanceRequestOrdersModel) return order.created; + if (order is GetCMCAllOrdersResponseModel) return order.created; + return null; + } + + void openRRT(BuildContext context) { if (appState.isAuthenticated) { if (agreedToTermsAndCondition == false) { dialogService.showErrorBottomSheet( @@ -1032,6 +1069,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { return; } placeValueInController(); + context.read().placeValueInController(); locationUtils!.getLocation( isShowConfirmDialog: true, onSuccess: (position) async { diff --git a/lib/features/health_trackers/health_trackers_repo.dart b/lib/features/health_trackers/health_trackers_repo.dart index 2a64fc46..52bc40f6 100644 --- a/lib/features/health_trackers/health_trackers_repo.dart +++ b/lib/features/health_trackers/health_trackers_repo.dart @@ -15,6 +15,9 @@ abstract class HealthTrackersRepo { /// Get blood sugar results (week, month, year). Future>> getDiabeticResults(); + Future>> downloadBloodSugarReport(); + Future>> downloadWeightReport(); + Future>> downloadBloodPressureReport(); /// Add new blood sugar result. Future>> addDiabeticResult({ @@ -211,6 +214,114 @@ class HealthTrackersRepoImp implements HealthTrackersRepo { } } + @override + Future>> downloadBloodSugarReport() async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.averageBloodSugarReportDownload, + body: {}, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + dynamic pdf = response['ReportPDFContent']; + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: pdf, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + + @override + Future>> downloadWeightReport() async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.averageWeightReportDownload, + body: {}, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + dynamic pdf = response['ReportPDFContent']; + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: pdf, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + @override + Future>> downloadBloodPressureReport() async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.averageBloodPressureReportDownload, + body: {}, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + dynamic pdf = response['ReportPDFContent']; + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: pdf, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + @override Future>> addDiabeticResult({ required String bloodSugarDateChart, diff --git a/lib/features/health_trackers/health_trackers_view_model.dart b/lib/features/health_trackers/health_trackers_view_model.dart index ab54f595..6b16fdc3 100644 --- a/lib/features/health_trackers/health_trackers_view_model.dart +++ b/lib/features/health_trackers/health_trackers_view_model.dart @@ -1,6 +1,10 @@ import 'dart:developer'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.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/features/health_trackers/health_trackers_repo.dart'; import 'package:hmg_patient_app_new/features/health_trackers/models/blood_pressure/blood_pressure_result.dart'; import 'package:hmg_patient_app_new/features/health_trackers/models/blood_pressure/month_blood_pressure_result_average.dart'; @@ -14,7 +18,9 @@ import 'package:hmg_patient_app_new/features/health_trackers/models/weight/month import 'package:hmg_patient_app_new/features/health_trackers/models/weight/week_weight_measurement_result_average.dart'; import 'package:hmg_patient_app_new/features/health_trackers/models/weight/weight_measurement_result.dart'; import 'package:hmg_patient_app_new/features/health_trackers/models/weight/year_weight_measurement_result_average.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; class HealthTrackersViewModel extends ChangeNotifier { HealthTrackersRepo healthTrackersRepo; @@ -28,13 +34,25 @@ class HealthTrackersViewModel extends ChangeNotifier { String? get errorMessage => _errorMessage; - List get durationFilters => ["Week", "Month", "Year"]; + final List durationFiltersEn = ["Week", "Month", "Year"]; + final List durationFiltersAr = ["أسبوع", "شهر", "سنة"]; + + List get durationFilters => getIt.get().isArabic() ? durationFiltersAr : durationFiltersEn; String _selectedDuration = "Week"; bool _isGraphView = true; String get selectedDurationFilter => _selectedDuration; + // Get the localized display value for the selected duration + String get selectedDurationFilterDisplay { + final index = durationFiltersEn.indexOf(_selectedDuration); + if (index >= 0) { + return getIt.get().isArabic() ? durationFiltersAr[index] : durationFiltersEn[index]; + } + return _selectedDuration; + } + bool get isGraphView => _isGraphView; final List bloodSugarUnit = ['mg/dlt', 'mol/L']; @@ -72,6 +90,10 @@ class HealthTrackersViewModel extends ChangeNotifier { "آخر", ]; + List getBloodSugarTimeList(){ + return getIt.get().isArabic()?bloodSugarMeasureTimeArList:bloodSugarMeasureTimeEnList; + } + // Setters with notification void setBloodSugarMeasureTime(String duration) async { _selectedBloodSugarMeasureTime = duration; @@ -80,7 +102,15 @@ class HealthTrackersViewModel extends ChangeNotifier { // Setters with notification void setFilterDuration(String duration) async { - _selectedDuration = duration; + // Map Arabic values to English keys + if (getIt.get().isArabic()) { + final index = durationFiltersAr.indexOf(duration); + if (index >= 0 && index < durationFiltersEn.length) { + _selectedDuration = durationFiltersEn[index]; + } + } else { + _selectedDuration = duration; + } notifyListeners(); } @@ -97,26 +127,73 @@ class HealthTrackersViewModel extends ChangeNotifier { // ==================== WEIGHT FORM FIELDS ==================== final List weightUnits = ['kg', 'lb']; + final List weightUnitsEn = ['kg', 'lb']; + final List weightUnitsAr = ['كجم', 'رطل']; + String _selectedWeightUnit = 'kg'; String get selectedWeightUnit => _selectedWeightUnit; + List getWeightUnitsList() { + return getIt.get().isArabic() ? weightUnitsAr : weightUnitsEn; + } + void setWeightUnit(String unit) { - _selectedWeightUnit = unit; + // Map Arabic values to English keys + if (getIt.get().isArabic()) { + final index = weightUnitsAr.indexOf(unit); + if (index >= 0 && index < weightUnitsEn.length) { + _selectedWeightUnit = weightUnitsEn[index]; + } + } else { + _selectedWeightUnit = unit; + } notifyListeners(); } + // Get the localized display value for the selected weight unit + String get selectedWeightUnitDisplay { + final index = weightUnitsEn.indexOf(_selectedWeightUnit); + if (index >= 0) { + return getIt.get().isArabic() ? weightUnitsAr[index] : weightUnitsEn[index]; + } + return _selectedWeightUnit; + } + // ==================== BLOOD PRESSURE FORM FIELDS ==================== - final List measuredArmList = ['Left Arm', 'Right Arm']; + final List measuredArmListEn = ['Left Arm', 'Right Arm']; + final List measuredArmListAr = ['الذراع الأيسر', 'الذراع الأيمن']; + String _selectedMeasuredArm = ''; String get selectedMeasuredArm => _selectedMeasuredArm; + List getMeasuredArmList() { + return getIt.get().isArabic() ? measuredArmListAr : measuredArmListEn; + } + void setMeasuredArm(String arm) { - _selectedMeasuredArm = arm; + // Map Arabic values to English keys + if (getIt.get().isArabic()) { + final index = measuredArmListAr.indexOf(arm); + if (index >= 0 && index < measuredArmListEn.length) { + _selectedMeasuredArm = measuredArmListEn[index]; + } + } else { + _selectedMeasuredArm = arm; + } notifyListeners(); } + // Get the localized display value for the selected measured arm + String get selectedMeasuredArmDisplay { + final index = measuredArmListEn.indexOf(_selectedMeasuredArm); + if (index >= 0) { + return getIt.get().isArabic() ? measuredArmListAr[index] : measuredArmListEn[index]; + } + return _selectedMeasuredArm; + } + // Text Controllers TextEditingController weightController = TextEditingController(); TextEditingController bloodSugarController = TextEditingController(); @@ -425,6 +502,91 @@ class HealthTrackersViewModel extends ChangeNotifier { } } + + Future downloadBloodSugerReport({ + Function(String)? onSuccess, + }) async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.generatingReport.tr()); + try { + final result = await healthTrackersRepo.downloadBloodSugarReport(); + LoaderBottomSheet.hideLoader(); + bool success = false; + + result.fold( + (failure) { + errorHandlerService.handleError(failure: failure); + }, + (apiModel) { + success = true; + if (onSuccess != null) onSuccess(apiModel.data??""); + }, + ); + + notifyListeners(); + + return success; + } catch (e) { + log('Error in sendWeightReportByEmail: $e'); + return false; + } + } + + Future downloadBloodPressureReport({ + Function(String)? onSuccess, + }) async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.generatingReport.tr()); + try { + final result = await healthTrackersRepo.downloadBloodPressureReport(); + LoaderBottomSheet.hideLoader(); + bool success = false; + + result.fold( + (failure) { + errorHandlerService.handleError(failure: failure); + }, + (apiModel) { + success = true; + if (onSuccess != null) onSuccess(apiModel.data??""); + }, + ); + + notifyListeners(); + + return success; + } catch (e) { + log('Error in sendWeightReportByEmail: $e'); + return false; + } + } + Future downloadWeightReport({ + Function(String)? onSuccess, + }) async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.generatingReport.tr()); + try { + final result = await healthTrackersRepo.downloadWeightReport(); + LoaderBottomSheet.hideLoader(); + bool success = false; + + result.fold( + (failure) { + errorHandlerService.handleError(failure: failure); + }, + (apiModel) { + success = true; + if (onSuccess != null) onSuccess(apiModel.data??""); + }, + ); + + notifyListeners(); + + return success; + } catch (e) { + log('Error in sendWeightReportByEmail: $e'); + return false; + } + } + + // ==================== BLOOD PRESSURE TRACKING METHODS ==================== /// Fetch blood pressure averages and results @@ -857,33 +1019,35 @@ class HealthTrackersViewModel extends ChangeNotifier { // Validation method String? _validateBloodSugarEntry(String dateTime) { + final isArabic = getIt.get().isArabic(); + // Validate blood sugar value if (bloodSugarController.text.trim().isEmpty) { - return "Please enter blood sugar value"; + return isArabic ? "الرجاء إدخال مستوى سكر الدم" : "Please enter blood sugar value"; } final bloodSugarValue = double.tryParse(bloodSugarController.text.trim()); if (bloodSugarValue == null) { - return "Please enter a valid number"; + return isArabic ? "الرجاء إدخال رقم صحيح" : "Please enter a valid number"; } if (bloodSugarValue <= 0) { - return "Blood sugar value must be greater than 0"; + return isArabic ? "يجب أن يكون مستوى سكر الدم أكبر من 0" : "Blood sugar value must be greater than 0"; } // Validate reasonable range (typical ranges) if (bloodSugarValue > 1000) { - return "Blood sugar value seems too high. Please check and enter again"; + return isArabic ? "مستوى سكر الدم يبدو مرتفع جداً. يرجى التحقق وإدخالها مرة أخرى" : "Blood sugar value seems too high. Please check and enter again"; } // Validate date time if (dateTime.trim().isEmpty) { - return "Please select date and time"; + return isArabic ? "الرجاء اختيار التاريخ والوقت" : "Please select date and time"; } // Validate measure time if (_selectedBloodSugarMeasureTime.isEmpty) { - return "Please select when the measurement was taken"; + return isArabic ? "الرجاء اختيار وقت القياس" : "Please select when the measurement was taken"; } return null; // No errors @@ -896,6 +1060,8 @@ class HealthTrackersViewModel extends ChangeNotifier { Function()? onSuccess, Function(String error)? onFailure, }) async { + final isArabic = getIt.get().isArabic(); + // Validate final validationError = _validateBloodSugarEntry(dateTime); if (validationError != null) { @@ -930,13 +1096,13 @@ class HealthTrackersViewModel extends ChangeNotifier { notifyListeners(); if (onSuccess != null) onSuccess(); } else { - _errorMessage = "Failed to save blood sugar entry. Please try again"; + _errorMessage = isArabic ? "فشل حفظ قراءة سكر الدم. يرجى المحاولة مرة أخرى" : "Failed to save blood sugar entry. Please try again"; notifyListeners(); if (onFailure != null) onFailure(_errorMessage!); } } catch (e) { log('Error in saveBloodSugarEntry: $e'); - _errorMessage = "An error occurred. Please try again"; + _errorMessage = isArabic ? "حدث خطأ. يرجى المحاولة مرة أخرى" : "An error occurred. Please try again"; isLoading = false; notifyListeners(); if (onFailure != null) onFailure(_errorMessage!); @@ -947,21 +1113,23 @@ class HealthTrackersViewModel extends ChangeNotifier { // Validate weight entry before saving String? _validateWeightEntry(String dateTime) { + final isArabic = getIt.get().isArabic(); + // Validate weight value final weightValue = weightController.text.trim(); if (weightValue.isEmpty) { - return "Please enter weight value"; + return isArabic ? "الرجاء إدخال قيمة الوزن" : "Please enter weight value"; } // Check if it's a valid number final parsedValue = double.tryParse(weightValue); if (parsedValue == null || parsedValue <= 0) { - return "Please enter a valid weight value"; + return isArabic ? "الرجاء إدخال قيمة وزن صحيحة" : "Please enter a valid weight value"; } // Validate date time if (dateTime.trim().isEmpty) { - return "Please select date and time"; + return isArabic ? "الرجاء تحديد التاريخ والوقت" : "Please select date and time"; } return null; // No errors @@ -973,6 +1141,8 @@ class HealthTrackersViewModel extends ChangeNotifier { Function()? onSuccess, Function(String error)? onFailure, }) async { + final isArabic = getIt.get().isArabic(); + // Validate final validationError = _validateWeightEntry(dateTime); if (validationError != null) { @@ -1005,13 +1175,13 @@ class HealthTrackersViewModel extends ChangeNotifier { notifyListeners(); if (onSuccess != null) onSuccess(); } else { - _errorMessage = "Failed to save weight entry. Please try again"; + _errorMessage = isArabic ? "فشل حفظ قراءة الوزن. يرجى المحاولة مرة أخرى" : "Failed to save weight entry. Please try again"; notifyListeners(); if (onFailure != null) onFailure(_errorMessage!); } } catch (e) { log('Error in saveWeightEntry: $e'); - _errorMessage = "An error occurred. Please try again"; + _errorMessage = isArabic ? "حدث خطأ. يرجى المحاولة مرة أخرى" : "An error occurred. Please try again"; isLoading = false; notifyListeners(); if (onFailure != null) onFailure(_errorMessage!); @@ -1022,34 +1192,36 @@ class HealthTrackersViewModel extends ChangeNotifier { // Validate blood pressure entry before saving String? _validateBloodPressureEntry(String dateTime) { + final isArabic = getIt.get().isArabic(); + // Validate systolic value final systolicValue = systolicController.text.trim(); if (systolicValue.isEmpty) { - return "Please enter systolic value"; + return isArabic ? "الرجاء إدخال القيمة الانقباضية" : "Please enter systolic value"; } final parsedSystolic = int.tryParse(systolicValue); if (parsedSystolic == null || parsedSystolic <= 0) { - return "Please enter a valid systolic value"; + return isArabic ? "الرجاء إدخال قيمة صحيحة للضغط الانقباضي" : "Please enter a valid systolic value"; } // Validate diastolic value final diastolicValue = diastolicController.text.trim(); if (diastolicValue.isEmpty) { - return "Please enter diastolic value"; + return isArabic ? "الرجاء إدخال قيمة الضغط الانبساطي" : "Please enter diastolic value"; } final parsedDiastolic = int.tryParse(diastolicValue); if (parsedDiastolic == null || parsedDiastolic <= 0) { - return "Please enter a valid diastolic value"; + return isArabic ? "الرجاء إدخال قيمة صحيحة للضغط الانبساطي" : "Please enter a valid diastolic value"; } // Validate arm selection if (_selectedMeasuredArm.isEmpty) { - return "Please select measured arm"; + return isArabic ? "الرجاء اختيار الذراع المقاسة" : "Please select measured arm"; } // Validate date time if (dateTime.trim().isEmpty) { - return "Please select date and time"; + return isArabic ? "الرجاء تحديد التاريخ والوقت" : "Please select date and time"; } return null; // No errors @@ -1061,6 +1233,8 @@ class HealthTrackersViewModel extends ChangeNotifier { Function()? onSuccess, Function(String error)? onFailure, }) async { + final isArabic = getIt.get().isArabic(); + // Validate final validationError = _validateBloodPressureEntry(dateTime); if (validationError != null) { @@ -1076,7 +1250,7 @@ class HealthTrackersViewModel extends ChangeNotifier { try { // Get measured arm index (0 = Left Arm, 1 = Right Arm) - final measuredArmIndex = measuredArmList.indexOf(_selectedMeasuredArm); + final measuredArmIndex = measuredArmListEn.indexOf(_selectedMeasuredArm); // Call API final success = await addOrUpdateBloodPressureResult( @@ -1097,13 +1271,13 @@ class HealthTrackersViewModel extends ChangeNotifier { notifyListeners(); if (onSuccess != null) onSuccess(); } else { - _errorMessage = "Failed to save blood pressure entry. Please try again"; + _errorMessage = isArabic ? "فشل حفظ قراءة ضغط الدم. يرجى المحاولة مرة أخرى" : "Failed to save blood pressure entry. Please try again"; notifyListeners(); if (onFailure != null) onFailure(_errorMessage!); } } catch (e) { log('Error in saveBloodPressureEntry: $e'); - _errorMessage = "An error occurred. Please try again"; + _errorMessage = isArabic ? "حدث خطأ. يرجى المحاولة مرة أخرى" : "An error occurred. Please try again"; isLoading = false; notifyListeners(); if (onFailure != null) onFailure(_errorMessage!); @@ -1118,4 +1292,30 @@ class HealthTrackersViewModel extends ChangeNotifier { diastolicController.dispose(); super.dispose(); } + + Future downloadReport(HealthTrackerTypeEnum trackerType, {required Function(dynamic report) onSuccess}) async { + switch (trackerType) { + case HealthTrackerTypeEnum.bloodSugar: + await downloadBloodSugerReport( + onSuccess : (value){ + onSuccess(value); + } + ); + break; + case HealthTrackerTypeEnum.bloodPressure: + await downloadBloodPressureReport( + onSuccess : (value){ + onSuccess(value); + } + ); + break; + case HealthTrackerTypeEnum.weightTracker: + await downloadWeightReport( + onSuccess : (value){ + onSuccess(value); + } + ); + break; + } + } } diff --git a/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart b/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart index ea0782bd..6fb24066 100644 --- a/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart +++ b/lib/features/hmg_services/models/ui_models/vital_sign_ui_model.dart @@ -11,6 +11,7 @@ enum VitalSignStatus { obese, overweight, underweight, + skinny, } /// UI-only helper model for Vital Sign cards. @@ -34,8 +35,9 @@ class VitalSignUiModel { /// /// Rules: /// - Height is always blue. - /// - High, Obese, Overweight => red scheme. - /// - Low, Underweight => yellow scheme. + /// - High, Obese => red scheme. + /// - Overweight => orange scheme. + /// - Low, Underweight, Skinny => yellow scheme. /// - Otherwise => green scheme (Normal). static VitalSignUiModel scheme({required VitalSignStatus? status, required String label}) { final l = label.toLowerCase(); @@ -50,10 +52,8 @@ class VitalSignUiModel { ); } - // High, Obese, Overweight => red scheme (health concerns) - if (status == VitalSignStatus.high || - status == VitalSignStatus.obese || - status == VitalSignStatus.overweight) { + // High, Obese => red scheme (health concerns) + if (status == VitalSignStatus.high || status == VitalSignStatus.obese) { return VitalSignUiModel( iconBg: AppColors.chipSecondaryLightRedColor, iconFg: AppColors.primaryRedColor, @@ -62,8 +62,20 @@ class VitalSignUiModel { ); } - // Low, Underweight => yellow scheme (warning) - if (status == VitalSignStatus.low || status == VitalSignStatus.underweight) { + // Overweight => orange scheme (warning) + if (status == VitalSignStatus.overweight) { + return VitalSignUiModel( + iconBg: AppColors.warningColorYellow.withValues(alpha: 0.12), + iconFg: AppColors.warningColorYellow, + chipBg: AppColors.warningColorYellow.withValues(alpha: 0.12), + chipFg: AppColors.warningColorYellow, + ); + } + + // Low, Underweight, Skinny => yellow scheme (warning) + if (status == VitalSignStatus.low || + status == VitalSignStatus.underweight || + status == VitalSignStatus.skinny) { final Color yellowBg = AppColors.highAndLow.withValues(alpha: 0.12); return VitalSignUiModel( iconBg: yellowBg, @@ -98,6 +110,8 @@ class VitalSignUiModel { return LocaleKeys.vitalSignOverweight.tr(); case VitalSignStatus.underweight: return LocaleKeys.vitalSignUnderweight.tr(); + case VitalSignStatus.skinny: + return LocaleKeys.vitalSignSkinny.tr(); } } @@ -131,22 +145,30 @@ class VitalSignUiModel { return null; } - // BMI >= 25 (Overweight or Obese) => High - if (bmiResult >= 25) { - return VitalSignStatus.high; + // BMI >= 30 (Obese) => Red + if (bmiResult >= 30) { + return VitalSignStatus.obese; + } + // BMI >= 25 (Overweight) => Orange + else if (bmiResult >= 25) { + return VitalSignStatus.overweight; } - // BMI >= 18.5 and < 25 => Normal + // BMI >= 18.5 (Normal) => Green else if (bmiResult >= 18.5) { return VitalSignStatus.normal; } - // BMI < 18.5 (Underweight) => Low + // BMI >= 16 (Underweight) => Yellow + else if (bmiResult >= 16) { + return VitalSignStatus.underweight; + } + // BMI < 16 (Skinny/Severely underweight) => Yellow else { - return VitalSignStatus.low; + return VitalSignStatus.skinny; } } /// Weight status based on BMI with detailed classification - /// Returns: Obese, Overweight, Normal, Underweight + /// Returns: Obese, Overweight, Normal, Underweight, Skinny static VitalSignStatus? weightStatus(dynamic bmi) { // Return null if BMI is not available or is 0 final double bmiResult = double.tryParse(bmi.toString()) ?? 0; @@ -155,15 +177,26 @@ class VitalSignUiModel { return null; } + // BMI >= 30 (Obese) => Red if (bmiResult >= 30) { return VitalSignStatus.obese; - } else if (bmiResult >= 25) { + } + // BMI >= 25 (Overweight) => Orange + else if (bmiResult >= 25) { return VitalSignStatus.overweight; - } else if (bmiResult >= 18.5) { + } + // BMI >= 18.5 (Normal) => Green + else if (bmiResult >= 18.5) { return VitalSignStatus.normal; - } else { + } + // BMI >= 16 (Underweight) => Yellow + else if (bmiResult >= 16) { return VitalSignStatus.underweight; } + // BMI < 16 (Skinny/Severely underweight) => Yellow + else { + return VitalSignStatus.skinny; + } } } diff --git a/lib/features/immediate_livecare/immediate_livecare_view_model.dart b/lib/features/immediate_livecare/immediate_livecare_view_model.dart index 6613b481..e61292d6 100644 --- a/lib/features/immediate_livecare/immediate_livecare_view_model.dart +++ b/lib/features/immediate_livecare/immediate_livecare_view_model.dart @@ -39,6 +39,7 @@ class ImmediateLiveCareViewModel extends ChangeNotifier { MyAppointmentsViewModel myAppointmentsViewModel; List immediateLiveCareClinicsList = []; + List mainLiveCareClinicList = []; bool isImmediateLiveCareClinicsLoading = false; int liveCareSelectedCallType = 0; // 1- Video, 2- Audio, 3- Phone late GetLiveCareClinicListResponseModel immediateLiveCareSelectedClinic; @@ -61,6 +62,7 @@ class ImmediateLiveCareViewModel extends ChangeNotifier { initImmediateLiveCare() { _appState = getIt(); immediateLiveCareClinicsList = []; + mainLiveCareClinicList = []; isImmediateLiveCareClinicsLoading = true; liveCareSelectedCallType = 0; // 1- Video, 2- Audio, 3- Phone immediateLiveCareSelectedClinic = GetLiveCareClinicListResponseModel(); @@ -90,9 +92,11 @@ class ImmediateLiveCareViewModel extends ChangeNotifier { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { - immediateLiveCareClinicsList = apiResponse.data!; + mainLiveCareClinicList = apiResponse.data!; - immediateLiveCareClinicsList.sort((a, b) => b.isOnline!.compareTo(a.isOnline!)); + mainLiveCareClinicList.sort((a, b) => b.isOnline!.compareTo(a.isOnline!)); + immediateLiveCareClinicsList.clear(); + immediateLiveCareClinicsList.addAll(mainLiveCareClinicList); isImmediateLiveCareClinicsLoading = false; notifyListeners(); @@ -103,6 +107,18 @@ class ImmediateLiveCareViewModel extends ChangeNotifier { }, ); } + void filterClinic(String query){ + if(query.isEmpty){ + immediateLiveCareClinicsList.clear(); + immediateLiveCareClinicsList.addAll(mainLiveCareClinicList); + }else{ + + // bool isArabic = getIt.get().isArabic(); + immediateLiveCareClinicsList = mainLiveCareClinicList.where((clinic) => clinic.serviceName!.toLowerCase().contains(query.toLowerCase()) || clinic.serviceNameN!.toLowerCase().contains(query.toLowerCase() + )).toList(); + } + notifyListeners(); + } Future getLiveCareImmediateAppointmentFees({Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = diff --git a/lib/features/insurance/insurance_view_model.dart b/lib/features/insurance/insurance_view_model.dart index 28210d39..621ebe4c 100644 --- a/lib/features/insurance/insurance_view_model.dart +++ b/lib/features/insurance/insurance_view_model.dart @@ -191,6 +191,7 @@ class InsuranceViewModel extends ChangeNotifier { notifyListeners(); }, (apiResponse) { + patientInsuranceApprovalsList =[]; if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { diff --git a/lib/features/location/location_view_model.dart b/lib/features/location/location_view_model.dart index 5b0eef6d..460eb0f1 100644 --- a/lib/features/location/location_view_model.dart +++ b/lib/features/location/location_view_model.dart @@ -18,7 +18,6 @@ class LocationViewModel extends ChangeNotifier { final ErrorHandlerService errorHandlerService; LocationViewModel({required this.locationRepo, required this.errorHandlerService}) { - placeValueInController(); } List predictions = []; @@ -31,7 +30,9 @@ class LocationViewModel extends ChangeNotifier { Completer? gmsController; Completer? hmsController; - + get isGMSAvailable { + return getIt.get().isGMSAvailable; + } HMSCameraServices.CameraPosition getHMSLocation() { return HMSCameraServices.CameraPosition(target: HMSCameraServices.LatLng(getIt().userLat, getIt().userLong), zoom: 18); } @@ -43,7 +44,7 @@ class LocationViewModel extends ChangeNotifier { } void placeValueInController() async { - if (await getIt().isGMSAvailable) { + if (isGMSAvailable) { gmsController = Completer(); } else { hmsController = Completer(); diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 05f82208..8b7ce7c3 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -41,6 +41,7 @@ class MedicalFileViewModel extends ChangeNotifier { List patientSickLeavesViewList = []; bool isSickLeavesSortByClinic = true; + // bool isSickLeavesDataNeedsReloading = true; List patientAllergiesList = []; @@ -165,13 +166,13 @@ class MedicalFileViewModel extends ChangeNotifier { setIsPatientSickLeaveListLoading(bool val) { // if (val && isSickLeavesDataNeedsReloading) { - patientSickLeaveList.clear(); - patientSickLeavesByClinic.clear(); - patientSickLeavesByHospital.clear(); - patientSickLeavesViewList.clear(); - patientSickLeavePDFBase64 = ""; - isSickLeavesSortByClinic = true; - isPatientSickLeaveListLoading = val; + patientSickLeaveList.clear(); + patientSickLeavesByClinic.clear(); + patientSickLeavesByHospital.clear(); + patientSickLeavesViewList.clear(); + patientSickLeavePDFBase64 = ""; + isSickLeavesSortByClinic = true; + isPatientSickLeaveListLoading = val; // } notifyListeners(); } @@ -456,6 +457,7 @@ class MedicalFileViewModel extends ChangeNotifier { patientIdenficationNumber: currentUser.patientIdentificationNo, emaiLAddress: currentUser.emailAddress, genderDescription: currentUser.genderDescription, + patientImageData: _appState.getProfileImageData, // Set main user's profile image ); // Clear and start fresh with current user @@ -499,6 +501,7 @@ class MedicalFileViewModel extends ChangeNotifier { genderDescription: element.genderDescription, genderImage: element.genderImage, emaiLAddress: element.emaiLAddress, + patientImageData: element.patientImageData, // Include profile image data ); if (isPending) { @@ -774,4 +777,41 @@ class MedicalFileViewModel extends ChangeNotifier { }, ); } + + /// Update family member's profile image in cached family files + void updateFamilyMemberProfileImage(int patientID, String imageData) { + try { + bool updated = false; + + // Update in patientFamilyFiles list + for (var i = 0; i < patientFamilyFiles.length; i++) { + if (patientFamilyFiles[i].patientId == patientID || + patientFamilyFiles[i].responseId == patientID) { + patientFamilyFiles[i].patientImageData = imageData; + updated = true; + print("✅ Updated profile image for family member in patientFamilyFiles (index: $i, patientID: $patientID)"); + break; + } + } + + // Update in pendingFamilyFiles list if exists + for (var i = 0; i < pendingFamilyFiles.length; i++) { + if (pendingFamilyFiles[i].patientId == patientID || + pendingFamilyFiles[i].responseId == patientID) { + pendingFamilyFiles[i].patientImageData = imageData; + print("✅ Updated profile image for pending family member (index: $i, patientID: $patientID)"); + break; + } + } + + if (updated) { + // Notify listeners to update UI + notifyListeners(); + } else { + print("⚠️ Family member not found in cache for patientID: $patientID"); + } + } catch (e) { + print("❌ Error updating family member profile image in cache: $e"); + } + } } diff --git a/lib/features/medical_file/models/family_file_response_model.dart b/lib/features/medical_file/models/family_file_response_model.dart index f4aa557d..59aa2bd2 100644 --- a/lib/features/medical_file/models/family_file_response_model.dart +++ b/lib/features/medical_file/models/family_file_response_model.dart @@ -25,6 +25,7 @@ class FamilyFileResponseModelLists { String? statusDescription; bool? isSuperUser = false; bool? isRequestFromMySide; + String? patientImageData; FamilyFileResponseModelLists( {this.id, @@ -50,7 +51,8 @@ class FamilyFileResponseModelLists { this.patientName, this.statusDescription, this.isSuperUser, - this.isRequestFromMySide}); + this.isRequestFromMySide, + this.patientImageData}); factory FamilyFileResponseModelLists.fromRawJson(String str) => FamilyFileResponseModelLists.fromJson(json.decode(str)); @@ -81,6 +83,7 @@ class FamilyFileResponseModelLists { statusDescription: json["StatusDescription"], isSuperUser: json["isSuperUser"] ?? false, isRequestFromMySide: json["isRequestFromMySide"] ?? false, + patientImageData: json["PatientImageData"], ); Map toJson() => { @@ -108,5 +111,6 @@ class FamilyFileResponseModelLists { "StatusDescription": statusDescription, "isSuperUser": isSuperUser, "isRequestFromMySide": isRequestFromMySide, + "PatientImageData": patientImageData, }; } diff --git a/lib/features/my_appointments/models/reminder_type.dart b/lib/features/my_appointments/models/reminder_type.dart new file mode 100644 index 00000000..f13f20a0 --- /dev/null +++ b/lib/features/my_appointments/models/reminder_type.dart @@ -0,0 +1,50 @@ +/// Enum to define different types of reminders +enum ReminderType { + /// Reminder for appointment schedule + appointment, + + /// Reminder for payment due + payment, + + /// Reminder for check-in + checkIn, +} + +extension ReminderTypeExtension on ReminderType { + /// Get display name for the reminder type + String get displayName { + switch (this) { + case ReminderType.appointment: + return 'Appointment Reminder'; + case ReminderType.payment: + return 'Payment Reminder'; + case ReminderType.checkIn: + return 'Check-In Reminder'; + } + } + + /// Get description for the reminder type + String get description { + switch (this) { + case ReminderType.appointment: + return 'Appointment scheduled reminder'; + case ReminderType.payment: + return 'Payment due reminder'; + case ReminderType.checkIn: + return 'Check-in reminder'; + } + } + + /// Get event ID prefix for calendar events + String get eventIdPrefix { + switch (this) { + case ReminderType.appointment: + return 'APPT'; + case ReminderType.payment: + return 'PAY'; + case ReminderType.checkIn: + return 'CHKIN'; + } + } +} + diff --git a/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart b/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart index cea2e64b..420c0482 100644 --- a/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart +++ b/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart @@ -75,6 +75,7 @@ class PatientAppointmentHistoryResponseModel { num? patientShareWithTax; num? patientTaxAmount; String? doctorNationalityFlagURL; + bool? isClinicReBookingAllowed; PatientAppointmentHistoryResponseModel({ this.setupID, @@ -152,6 +153,7 @@ class PatientAppointmentHistoryResponseModel { this.patientShareWithTax, this.patientTaxAmount, this.doctorNationalityFlagURL, + this.isClinicReBookingAllowed, }); PatientAppointmentHistoryResponseModel.fromJson(Map json) { @@ -241,6 +243,7 @@ class PatientAppointmentHistoryResponseModel { patientShareWithTax = json['PatientShareWithTax']; patientTaxAmount = json['PatientTaxAmount']; doctorNationalityFlagURL = json['DoctorNationalityFlagURL']; + isClinicReBookingAllowed = json['IsClinicReBookingAllowed']; } Map toJson() { @@ -313,6 +316,7 @@ class PatientAppointmentHistoryResponseModel { data['RemaniningHoursTocanPay'] = this.remaniningHoursTocanPay; data['SMSButtonVisable'] = this.sMSButtonVisable; data['ServiceID'] = this.serviceID; + data['IsClinicReBookingAllowed'] = this.isClinicReBookingAllowed; return data; } } diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index d4669767..541e8878 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -847,7 +847,7 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { // final queueList = list.map((item) => PatientQueueDetails.fromJson(item as Map)).toList().cast(); final queueList = PatientQueueDetails.fromJson(queueData); - queueList.patientQueueData!.isClinicConfigured = 1; + // queueList.patientQueueData!.isClinicConfigured = 1; apiResponse = GenericApiModel( messageStatus: messageStatus, diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 73f8d331..942a51e6 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -2,11 +2,13 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/utils/calender_utils_new.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_favorite_doctors_list.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/appointemnet_filters.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/reminder_type.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_rated_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart'; @@ -19,6 +21,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; @@ -1109,4 +1112,409 @@ class MyAppointmentsViewModel extends ChangeNotifier { }, ); } + + /// Creates a reminder for the appointment + /// Shows a bottom sheet to select reminder time and creates calendar event + /// [reminderType] specifies the type of reminder (appointment, payment, or check-in) + /// For payment and check-in reminders, shows only 1, 2, 3 hours before appointment + /// Automatically removes previous reminders when creating a new one + Future createReminder({ + required BuildContext context, + required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, + ReminderType reminderType = ReminderType.appointment, + Function(bool)? onSuccess, + Function(String)? onError, + }) async { + try { + CalenderUtilsNew calender = CalenderUtilsNew.instance; + DateTime endDate = DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate); + + // Generate title and description based on reminder type + String title; + String description; + String eventIdPrefix = reminderType.eventIdPrefix; + + switch (reminderType) { + case ReminderType.appointment: + title = "Appointment with ${patientAppointmentHistoryResponseModel.doctorNameObj}"; + description = "${patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${patientAppointmentHistoryResponseModel.appointmentDate}"; + break; + case ReminderType.payment: + title = "Payment Due for Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}"; + description = "Payment reminder for appointment with ${patientAppointmentHistoryResponseModel.doctorNameObj} at ${patientAppointmentHistoryResponseModel.projectName}"; + break; + case ReminderType.checkIn: + title = "Check-In Reminder for Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}"; + description = "Don't forget to check in for your appointment with ${patientAppointmentHistoryResponseModel.doctorNameObj}"; + break; + } + + // For payment and check-in, remove existing reminders first + if (reminderType == ReminderType.payment || reminderType == ReminderType.checkIn) { + // Remove all existing reminder types + await _removeAllRemindersForAppointment(patientAppointmentHistoryResponseModel); + } + + // For payment and check-in reminders, use custom hour-based options + if (reminderType == ReminderType.payment || reminderType == ReminderType.checkIn) { + _showHourBasedReminderSheet( + context: context, + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + reminderType: reminderType, + title: title, + description: description, + eventIdPrefix: eventIdPrefix, + endDate: endDate, + onSuccess: onSuccess, + onError: onError, + ); + } else { + // For appointment reminders, use the standard reminder sheet with all options + BottomSheetUtils().showReminderBottomSheet( + context, + endDate, + patientAppointmentHistoryResponseModel.doctorNameObj ?? "", + "${patientAppointmentHistoryResponseModel.appointmentNo}", + "", + "", + title: title, + description: description, + onSuccess: () { + setAppointmentReminder(true, patientAppointmentHistoryResponseModel); + if (onSuccess != null) { + onSuccess(true); + } + }, + isMultiAllowed: true, + onMultiDateSuccess: (int selectedIndex) async { + String eventTitle; + String eventDescription; + + switch (reminderType) { + case ReminderType.appointment: + eventTitle = "Appointment Reminder with ${patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}"; + eventDescription = "Appointment Reminder with ${patientAppointmentHistoryResponseModel.doctorNameObj} in ${patientAppointmentHistoryResponseModel.projectName}"; + break; + case ReminderType.payment: + eventTitle = "Payment Due Reminder for Appointment #${patientAppointmentHistoryResponseModel.appointmentNo} with ${patientAppointmentHistoryResponseModel.doctorNameObj}"; + eventDescription = "Payment reminder for ${patientAppointmentHistoryResponseModel.doctorNameObj} at ${patientAppointmentHistoryResponseModel.projectName}"; + break; + case ReminderType.checkIn: + eventTitle = "Check-In Reminder for Appointment #${patientAppointmentHistoryResponseModel.appointmentNo} with ${patientAppointmentHistoryResponseModel.doctorNameObj}"; + eventDescription = "Check-in reminder for appointment with ${patientAppointmentHistoryResponseModel.doctorNameObj} at ${patientAppointmentHistoryResponseModel.projectName}"; + break; + } + + bool isEventAddedOrRemoved = await calender.createOrUpdateEvent( + title: eventTitle, + description: eventDescription, + scheduleDateTime: DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), + eventId: "${eventIdPrefix}_${patientAppointmentHistoryResponseModel.appointmentNo}", + location: patientAppointmentHistoryResponseModel.projectName ?? '', + reminderMinutes: selectedIndex, + ); + + setAppointmentReminder(isEventAddedOrRemoved, patientAppointmentHistoryResponseModel); + + if (onSuccess != null) { + onSuccess(isEventAddedOrRemoved); + } + }, + ); + } + } catch (e) { + if (onError != null) { + onError(e.toString()); + } + } + } + + /// Removes all reminder types for an appointment (internal helper) + Future _removeAllRemindersForAppointment(PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel) async { + CalenderUtilsNew calender = CalenderUtilsNew.instance; + + // Remove all possible reminder types + for (var type in ReminderType.values) { + try { + await calender.checkAndRemove( + id: "${type.eventIdPrefix}_${patientAppointmentHistoryResponseModel.appointmentNo}", + ); + } catch (e) { + // Ignore errors if reminder doesn't exist + debugPrint("No existing ${type.displayName} to remove"); + } + } + } + + /// Shows hour-based reminder selection for payment and check-in reminders + void _showHourBasedReminderSheet({ + required BuildContext context, + required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, + required ReminderType reminderType, + required String title, + required String description, + required String eventIdPrefix, + required DateTime endDate, + Function(bool)? onSuccess, + Function(String)? onError, + }) { + showCommonBottomSheetWithoutHeight( + context, + title: "Select Reminder Time", + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Text( + "When would you like to be reminded?", + style: TextStyle( + fontSize: 14.f, + color: AppColors.greyTextColor, + ), + textAlign: TextAlign.center, + ), + ), + SizedBox(height: 24.h), + _buildHourOption( + context: context, + hours: 1, + icon: Icons.alarm, + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + reminderType: reminderType, + title: title, + description: description, + eventIdPrefix: eventIdPrefix, + endDate: endDate, + onSuccess: onSuccess, + onError: onError, + ), + SizedBox(height: 12.h), + _buildHourOption( + context: context, + hours: 2, + icon: Icons.alarm, + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + reminderType: reminderType, + title: title, + description: description, + eventIdPrefix: eventIdPrefix, + endDate: endDate, + onSuccess: onSuccess, + onError: onError, + ), + SizedBox(height: 12.h), + _buildHourOption( + context: context, + hours: 3, + icon: Icons.alarm, + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + reminderType: reminderType, + title: title, + description: description, + eventIdPrefix: eventIdPrefix, + endDate: endDate, + onSuccess: onSuccess, + onError: onError, + ), + ], + ), + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + + /// Builds an hour option button for payment/check-in reminders + Widget _buildHourOption({ + required BuildContext context, + required int hours, + required IconData icon, + required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, + required ReminderType reminderType, + required String title, + required String description, + required String eventIdPrefix, + required DateTime endDate, + Function(bool)? onSuccess, + Function(String)? onError, + }) { + return GestureDetector( + onTap: () async { + Navigator.of(context).pop(); // Close the selection sheet + + // Create reminder with selected hours (converted to minutes) + await _createReminderWithHours( + hours: hours, + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + reminderType: reminderType, + title: title, + description: description, + eventIdPrefix: eventIdPrefix, + endDate: endDate, + onSuccess: onSuccess, + onError: onError, + ); + }, + child: Container( + padding: EdgeInsets.all(16.h), + decoration: BoxDecoration( + color: AppColors.whiteColor, + border: Border.all(color: AppColors.borderOnlyColor), + borderRadius: BorderRadius.circular(12.r), + ), + child: Row( + children: [ + Container( + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: AppColors.primaryRedColor.withAlpha(26), + borderRadius: BorderRadius.circular(8.r), + ), + child: Icon( + icon, + color: AppColors.primaryRedColor, + size: 24.h, + ), + ), + SizedBox(width: 12.w), + Expanded( + child: Text( + "$hours ${hours == 1 ? 'Hour' : 'Hours'} Before Appointment", + style: TextStyle( + fontSize: 16.f, + fontWeight: FontWeight.w600, + color: AppColors.textColor, + ), + ), + ), + Icon( + Icons.arrow_forward_ios, + size: 16.h, + color: AppColors.greyTextColor, + ), + ], + ), + ), + ); + } + + /// Creates reminder with specific hours before appointment + Future _createReminderWithHours({ + required int hours, + required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, + required ReminderType reminderType, + required String title, + required String description, + required String eventIdPrefix, + required DateTime endDate, + Function(bool)? onSuccess, + Function(String)? onError, + }) async { + try { + CalenderUtilsNew calender = CalenderUtilsNew.instance; + + // Convert hours to minutes + int reminderMinutes = hours * 60; + + String eventTitle; + String eventDescription; + + switch (reminderType) { + case ReminderType.appointment: + eventTitle = "Appointment Reminder with ${patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}"; + eventDescription = "Appointment Reminder with ${patientAppointmentHistoryResponseModel.doctorNameObj} in ${patientAppointmentHistoryResponseModel.projectName}"; + break; + case ReminderType.payment: + eventTitle = "Payment Due - $hours ${hours == 1 ? 'hour' : 'hours'} before Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}"; + eventDescription = "Payment reminder for ${patientAppointmentHistoryResponseModel.doctorNameObj} at ${patientAppointmentHistoryResponseModel.projectName}"; + break; + case ReminderType.checkIn: + eventTitle = "Check-In Now - $hours ${hours == 1 ? 'hour' : 'hours'} before Appointment #${patientAppointmentHistoryResponseModel.appointmentNo}"; + eventDescription = "Check-in reminder for appointment with ${patientAppointmentHistoryResponseModel.doctorNameObj} at ${patientAppointmentHistoryResponseModel.projectName}"; + break; + } + + bool isEventAddedOrRemoved = await calender.createOrUpdateEvent( + title: eventTitle, + description: eventDescription, + scheduleDateTime: DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), + eventId: "${eventIdPrefix}_${patientAppointmentHistoryResponseModel.appointmentNo}", + location: patientAppointmentHistoryResponseModel.projectName ?? '', + reminderMinutes: reminderMinutes, + ); + + setAppointmentReminder(isEventAddedOrRemoved, patientAppointmentHistoryResponseModel); + + if (onSuccess != null) { + onSuccess(isEventAddedOrRemoved); + } + } catch (e) { + if (onError != null) { + onError(e.toString()); + } + } + } + + /// Deletes the reminder for the appointment + /// Removes the calendar event associated with the appointment + /// [reminderType] specifies the type of reminder to delete (appointment, payment, or check-in) + Future deleteReminder({ + required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, + ReminderType reminderType = ReminderType.appointment, + Function(bool)? onSuccess, + Function(String)? onError, + }) async { + try { + CalenderUtilsNew calender = CalenderUtilsNew.instance; + String eventIdPrefix = reminderType.eventIdPrefix; + + bool isEventRemoved = await calender.checkAndRemove( + id: "${eventIdPrefix}_${patientAppointmentHistoryResponseModel.appointmentNo}", + ); + + // Update the reminder status (inverted because we're removing) + setAppointmentReminder(!isEventRemoved, patientAppointmentHistoryResponseModel); + + if (onSuccess != null) { + onSuccess(isEventRemoved); + } + } catch (e) { + if (onError != null) { + onError(e.toString()); + } + } + } + + /// Toggle reminder for an appointment + /// Creates or deletes reminder based on the current state + /// [reminderType] specifies the type of reminder (appointment, payment, or check-in) + Future toggleReminder({ + required BuildContext context, + required bool shouldCreateReminder, + required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, + required ReminderType reminderType , + Function(bool)? onSuccess, + Function(String)? onError, + }) async { + if (shouldCreateReminder) { + await createReminder( + context: context, + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + reminderType: reminderType, + onSuccess: onSuccess, + onError: onError, + ); + } else { + await deleteReminder( + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + reminderType: reminderType, + onSuccess: onSuccess, + onError: onError, + ); + } + } } diff --git a/lib/features/my_invoices/my_invoices_view_model.dart b/lib/features/my_invoices/my_invoices_view_model.dart index cd18026c..631efd5b 100644 --- a/lib/features/my_invoices/my_invoices_view_model.dart +++ b/lib/features/my_invoices/my_invoices_view_model.dart @@ -52,6 +52,8 @@ class MyInvoicesViewModel extends ChangeNotifier { notifyListeners(); }, (apiResponse) { + _originalInvoicesList =[]; + allInvoicesList =[]; if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index b9c29072..c374b3e2 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -20,6 +20,7 @@ import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/map/map_utility_screen.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:provider/provider.dart'; class PrescriptionsViewModel extends ChangeNotifier { bool isPrescriptionsOrdersLoading = false; @@ -255,7 +256,9 @@ class PrescriptionsViewModel extends ChangeNotifier { ); } - void initiatePrescriptionDelivery() async { + void initiatePrescriptionDelivery(BuildContext context) async { + context.read().placeValueInController(); + getIt.get().getLocation( isShowConfirmDialog: true, onSuccess: (position) async { @@ -303,6 +306,23 @@ class PrescriptionsViewModel extends ChangeNotifier { } else if (apiResponse.messageStatus == 1) { isPrescriptionsDeliveryOrdersLoading = false; prescriptionsOrderList = apiResponse.data!; + + // Sort by created date (newest first) + prescriptionsOrderList.sort((a, b) { + try { + if (a.created == null && b.created == null) return 0; + if (a.created == null) return 1; + if (b.created == null) return -1; + + DateTime dateA = DateTime.parse(a.created!); + DateTime dateB = DateTime.parse(b.created!); + return dateB.compareTo(dateA); // Descending order (newest first) + } catch (e) { + print('Error parsing date for sorting: $e'); + return 0; + } + }); + notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); diff --git a/lib/features/profile_settings/profile_settings_repo.dart b/lib/features/profile_settings/profile_settings_repo.dart index d86bd7fd..113a52c0 100644 --- a/lib/features/profile_settings/profile_settings_repo.dart +++ b/lib/features/profile_settings/profile_settings_repo.dart @@ -151,7 +151,7 @@ class ProfileSettingsRepoImp implements ProfileSettingsRepo { } @override - Future>> getProfileImage({ + Future>> getProfileImage({ required int patientID, }) async { final Map body = { diff --git a/lib/features/profile_settings/profile_settings_view_model.dart b/lib/features/profile_settings/profile_settings_view_model.dart index 77960569..5d2f58d4 100644 --- a/lib/features/profile_settings/profile_settings_view_model.dart +++ b/lib/features/profile_settings/profile_settings_view_model.dart @@ -3,6 +3,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/models/get_patient_info_response_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_repo.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; @@ -188,19 +189,15 @@ class ProfileSettingsViewModel extends ChangeNotifier { Function(String)? onError, bool forceRefresh = false, // Add flag to force refresh }) async { - // Skip API call if image is already loaded and not forcing refresh - if (!forceRefresh && GetIt.instance().getProfileImageData != null) { - // Use cached data - profileImageData = GetIt.instance().getProfileImageData; - if (onSuccess != null) { - onSuccess(profileImageData); - } - return; - } + print('🌐 Fetching profile image from API for patient: $patientID (forceRefresh: $forceRefresh)'); + + // IMPORTANT: Always clear old data BEFORE fetching + // Do NOT use cached data - always fetch fresh from API + profileImageData = null; isProfileImageLoading = true; profileImageError = null; - notifyListeners(); + notifyListeners(); // Notify to show loading state final result = await profileSettingsRepo.getProfileImage(patientID: patientID); @@ -208,6 +205,11 @@ class ProfileSettingsViewModel extends ChangeNotifier { (failure) { isProfileImageLoading = false; profileImageError = failure.message; + print('❌ Failed to fetch profile image: ${failure.message}'); + + // Ensure data is cleared on failure + profileImageData = null; + notifyListeners(); if (onError != null) { onError(failure.message); @@ -215,43 +217,48 @@ class ProfileSettingsViewModel extends ChangeNotifier { }, (response) { isProfileImageLoading = false; + // Extract image data from response if (response.data != null && response.data['Patient_GetProfileImageDataList'] != null) { var imageList = response.data['Patient_GetProfileImageDataList']; if (imageList is List && imageList.isNotEmpty) { - profileImageData = imageList[0]['PatientsProfileImageData']; - // Store in AppState for global access - GetIt.instance().setProfileImageData = profileImageData; + profileImageData = imageList[0]['ImageData']; + + if (profileImageData != null && profileImageData!.isNotEmpty) { + // ONLY store in ViewModel - AppState will be updated separately by auth flow + print('✅ Profile image loaded from API (length: ${profileImageData!.length})'); + } else { + profileImageData = null; + print('⚠️ Profile image data is empty in API response'); + } } else { profileImageData = null; - GetIt.instance().setProfileImageData = null; + print('⚠️ Profile image list is empty in API response'); } } else { profileImageData = null; - GetIt.instance().setProfileImageData = null; + print('⚠️ No profile image data in API response'); } - notifyListeners(); + + notifyListeners(); // Notify to update UI with new/null data onSuccess?.call(response.data); }, ); } - /// Clear cached profile image data + /// Clear profile image data from ViewModel void clearProfileImageCache() { + print('🧹 Clearing profile image from ViewModel'); profileImageData = null; - GetIt.instance().setProfileImageData = null; notifyListeners(); } - /// Sync profile image data from AppState + /// Sync profile image data from AppState - DISABLED + /// Profile Settings screen uses only ViewModel data from API void syncProfileImageFromAppState() { - final appStateImageData = GetIt.instance().getProfileImageData; - if (appStateImageData != null && appStateImageData.isNotEmpty) { - if (profileImageData != appStateImageData) { - profileImageData = appStateImageData; - notifyListeners(); - } - } + // DO NOTHING - Profile Settings uses only direct API data + // No syncing from AppState to avoid stale cache issues + print('⚠️ syncProfileImageFromAppState called but disabled - use API data only'); } Future uploadProfileImage({ @@ -286,6 +293,16 @@ class ProfileSettingsViewModel extends ChangeNotifier { profileImageData = imageData; // Store in AppState for global access GetIt.instance().setProfileImageData = imageData; + + // Update the family files cache with the new profile image + try { + final medicalFileViewModel = GetIt.instance.get(); + medicalFileViewModel.updateFamilyMemberProfileImage(patientID, imageData); + print("✅ Updated profile image in family files cache for patient: $patientID"); + } catch (e) { + print("⚠️ Could not update family files cache: $e"); + } + notifyListeners(); onSuccess?.call(response.data); }, diff --git a/lib/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart b/lib/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart index e838f187..3f523428 100644 --- a/lib/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart +++ b/lib/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart @@ -2,9 +2,19 @@ import 'dart:convert'; class RadiologyAiAnalysisResponse { String? summary; + String? labResults; + String? aiPredictions; + String? riskFactors; + String? prevention; + int? languageId; RadiologyAiAnalysisResponse({ this.summary, + this.labResults, + this.aiPredictions, + this.riskFactors, + this.prevention, + this.languageId, }); factory RadiologyAiAnalysisResponse.fromRawJson(String str) => RadiologyAiAnalysisResponse.fromJson(json.decode(str)); @@ -12,11 +22,20 @@ class RadiologyAiAnalysisResponse { String toRawJson() => json.encode(toJson()); factory RadiologyAiAnalysisResponse.fromJson(Map json) => RadiologyAiAnalysisResponse( - summary: json["summary"], - ); + summary: json["summary"], + labResults: json["lab_results"], + aiPredictions: json["ai_predictions"], + riskFactors: json["riskFactors"], + prevention: json["prevention"], + languageId: json["language_id"], + ); Map toJson() => { - "summary": summary, - }; + "summary": summary, + "lab_results": labResults, + "ai_predictions": aiPredictions, + "riskFactors": riskFactors, + "prevention": prevention, + "language_id": languageId, + }; } - diff --git a/lib/features/radiology/radiology_repo.dart b/lib/features/radiology/radiology_repo.dart index 30b078ef..7374b247 100644 --- a/lib/features/radiology/radiology_repo.dart +++ b/lib/features/radiology/radiology_repo.dart @@ -42,7 +42,7 @@ class RadiologyRepoImp implements RadiologyRepo { failure = failureType; }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { - final radOrders; + List radOrders = []; try { if (response['FinalRadiologyList'] != null && response['FinalRadiologyList'].length != 0) { final list = response['FinalRadiologyList']; @@ -52,10 +52,18 @@ class RadiologyRepoImp implements RadiologyRepo { .cast(); } else { final list = response['FinalRadiologyListAPI']; - radOrders = list - .map((item) => PatientRadiologyResponseModel.fromJson(item as Map)) - .toList() - .cast(); + if(list != null && list.length != 0) { + radOrders = list + .map((item) => PatientRadiologyResponseModel.fromJson(item as Map)) + .toList() + .cast(); + } + } + + // Check if radOrders is empty and set failure + if (radOrders.isEmpty) { + failure = ServerFailure(errorMessage ?? "No radiology orders found"); + return; } apiResponse = GenericApiModel>( @@ -203,6 +211,12 @@ class RadiologyRepoImp implements RadiologyRepo { } } + // Check if radOrders is empty and set failure + if (radOrders.isEmpty) { + failure = ServerFailure(errorMessage ?? "No radiology orders found for this appointment"); + return; + } + apiResponse = GenericApiModel>( messageStatus: messageStatus, statusCode: statusCode, diff --git a/lib/features/radiology/radiology_view_model.dart b/lib/features/radiology/radiology_view_model.dart index 7386a5a0..cf795a1d 100644 --- a/lib/features/radiology/radiology_view_model.dart +++ b/lib/features/radiology/radiology_view_model.dart @@ -1,12 +1,10 @@ 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/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/radiology/radiology_ai_analysis_detailed_page.dart'; import 'package:hmg_patient_app_new/presentation/radiology/radiology_result_page.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -216,12 +214,11 @@ class RadiologyViewModel extends ChangeNotifier { void setIsRadiologyAIAnalysisNeedsToBeShown(bool value) { isRadiologyAIAnalysisNeedsToBeShown = value; - notifyListeners(); } void closeRadiologyAIAnalysis() { radiologyAiAnalysisResponse = null; - notifyListeners(); + } Future getRadiologyAiAnalysis({required String reportData, required String loadingText, required int langId}) async { @@ -229,7 +226,6 @@ class RadiologyViewModel extends ChangeNotifier { loadingText: loadingText, showCloseButton: true, onCloseTap: () { - setIsRadiologyAIAnalysisNeedsToBeShown(false); }, ); @@ -248,6 +244,10 @@ class RadiologyViewModel extends ChangeNotifier { if (isRadiologyAIAnalysisNeedsToBeShown) { radiologyAiAnalysisResponse = apiResponse.data; notifyListeners(); + // Navigate to detailed page + navigationService.push( + CustomPageRoute(direction: AxisDirection.down, page: RadiologyAiAnalysisDetailedPage()), + ); } } }, diff --git a/lib/features/water_monitor/water_monitor_view_model.dart b/lib/features/water_monitor/water_monitor_view_model.dart index 1ef2ef65..5234a559 100644 --- a/lib/features/water_monitor/water_monitor_view_model.dart +++ b/lib/features/water_monitor/water_monitor_view_model.dart @@ -40,7 +40,20 @@ class WaterMonitorViewModel extends ChangeNotifier { // Units final List heightUnits = ['cm', 'm', 'ft', 'in']; + final List heightUnitsEn = ['cm', 'm', 'ft', 'in']; + final List heightUnitsAr = ['سم', 'م', 'قدم', 'بوصة']; + final List weightUnits = ['kg', 'lb']; + final List weightUnitsEn = ['kg', 'lb']; + final List weightUnitsAr = ['كجم', 'رطل']; + + List getHeightUnitsList() { + return getIt.get().isArabic() ? heightUnitsAr : heightUnitsEn; + } + + List getWeightUnitsList() { + return getIt.get().isArabic() ? weightUnitsAr : weightUnitsEn; + } // Selected values String _selectedHeightUnit = 'cm'; @@ -62,8 +75,26 @@ class WaterMonitorViewModel extends ChangeNotifier { String get selectedHeightUnit => _selectedHeightUnit; + // Get the localized display value for the selected height unit + String get selectedHeightUnitDisplay { + final index = heightUnitsEn.indexOf(_selectedHeightUnit); + if (index >= 0) { + return getIt.get().isArabic() ? heightUnitsAr[index] : heightUnitsEn[index]; + } + return _selectedHeightUnit; + } + String get selectedWeightUnit => _selectedWeightUnit; + // Get the localized display value for the selected weight unit + String get selectedWeightUnitDisplay { + final index = weightUnitsEn.indexOf(_selectedWeightUnit); + if (index >= 0) { + return getIt.get().isArabic() ? weightUnitsAr[index] : weightUnitsEn[index]; + } + return _selectedWeightUnit; + } + String get selectedActivityLevel => _selectedActivityLevel; String get selectedNumberOfReminders => _selectedNumberOfReminders; @@ -75,17 +106,75 @@ class WaterMonitorViewModel extends ChangeNotifier { bool get isGraphView => _isGraphView; // Activity level options - List get activityLevels => - ["Almost Inactive (no exercise)", "Lightly active", "Lightly active (1-3) days per week", "Super active (very hard exercise)"]; + final List activityLevelsEn = [ + "Almost Inactive (no exercise)", + "Lightly active", + "Lightly active (1-3) days per week", + "Super active (very hard exercise)" + ]; + + final List activityLevelsAr = [ + "غير نشط تقريباً (بدون تمرين)", + "نشط قليلاً", + "نشط قليلاً (1-3) أيام في الأسبوع", + "نشط جداً (تمرين شاق جداً)" + ]; + + List get activityLevels => getIt.get().isArabic() ? activityLevelsAr : activityLevelsEn; + + // Get the localized display value for the selected activity level + String get selectedActivityLevelDisplay { + final index = activityLevelsEn.indexOf(_selectedActivityLevel); + if (index >= 0) { + return getIt.get().isArabic() ? activityLevelsAr[index] : activityLevelsEn[index]; + } + return _selectedActivityLevel; + } // Reminder options - List get reminderOptions => ["1 Time", "2 Time", "3 Time", "4 Time", "5 Time", "6 Time"]; + final List reminderOptionsEn = ["1 Time", "2 Time", "3 Time", "4 Time", "5 Time", "6 Time"]; + final List reminderOptionsAr = ["مرة واحدة", "مرتين", "3 مرات", "4 مرات", "5 مرات", "6 مرات"]; + + List get reminderOptions => getIt.get().isArabic() ? reminderOptionsAr : reminderOptionsEn; + + // Get the localized display value for the selected number of reminders + String get selectedNumberOfRemindersDisplay { + final index = reminderOptionsEn.indexOf(_selectedNumberOfReminders); + if (index >= 0) { + return getIt.get().isArabic() ? reminderOptionsAr[index] : reminderOptionsEn[index]; + } + return _selectedNumberOfReminders; + } // Gender options - List get genderOptions => ["Male", "Female"]; + final List genderOptionsEn = ["Male", "Female"]; + final List genderOptionsAr = ["ذكر", "أنثى"]; + + List get genderOptions => getIt.get().isArabic() ? genderOptionsAr : genderOptionsEn; + + // Get the localized display value for the selected gender + String get selectedGenderDisplay { + final index = genderOptionsEn.indexOf(_selectedGender); + if (index >= 0) { + return getIt.get().isArabic() ? genderOptionsAr[index] : genderOptionsEn[index]; + } + return _selectedGender; + } + + // Duration Options + final List durationFiltersEn = ["Daily", "Weekly", "Monthly"]; + final List durationFiltersAr = ["يومي", "أسبوعي", "شهري"]; -//Duration Options - List get durationFilters => ["Daily", "Weekly", "Monthly"]; + List get durationFilters => getIt.get().isArabic() ? durationFiltersAr : durationFiltersEn; + + // Get the localized display value for the selected duration + String get selectedDurationFilterDisplay { + final index = durationFiltersEn.indexOf(_selectedDuration); + if (index >= 0) { + return getIt.get().isArabic() ? durationFiltersAr[index] : durationFiltersEn[index]; + } + return _selectedDuration; + } // Network/data final AppState _appState = GetIt.instance(); @@ -503,7 +592,15 @@ class WaterMonitorViewModel extends ChangeNotifier { // Setters with notification void setFilterDuration(String duration) async { - _selectedDuration = duration; + // Map Arabic values to English keys + if (getIt.get().isArabic()) { + final index = durationFiltersAr.indexOf(duration); + if (index >= 0 && index < durationFiltersEn.length) { + _selectedDuration = durationFiltersEn[index]; + } + } else { + _selectedDuration = duration; + } notifyListeners(); // Fetch new progress data when duration filter changes await fetchUserProgressForMonitoring(); @@ -517,27 +614,67 @@ class WaterMonitorViewModel extends ChangeNotifier { // Setters with notification void setGender(String gender) { - _selectedGender = gender; + // Map Arabic values to English keys + if (getIt.get().isArabic()) { + final index = genderOptionsAr.indexOf(gender); + if (index >= 0 && index < genderOptionsEn.length) { + _selectedGender = genderOptionsEn[index]; + } + } else { + _selectedGender = gender; + } notifyListeners(); } void setHeightUnit(String unit) { - _selectedHeightUnit = unit; + // Map Arabic values to English keys + if (getIt.get().isArabic()) { + final index = heightUnitsAr.indexOf(unit); + if (index >= 0 && index < heightUnitsEn.length) { + _selectedHeightUnit = heightUnitsEn[index]; + } + } else { + _selectedHeightUnit = unit; + } notifyListeners(); } void setWeightUnit(String unit) { - _selectedWeightUnit = unit; + // Map Arabic values to English keys + if (getIt.get().isArabic()) { + final index = weightUnitsAr.indexOf(unit); + if (index >= 0 && index < weightUnitsEn.length) { + _selectedWeightUnit = weightUnitsEn[index]; + } + } else { + _selectedWeightUnit = unit; + } notifyListeners(); } void setActivityLevel(String level) { - _selectedActivityLevel = level; + // Map Arabic values to English keys + if (getIt.get().isArabic()) { + final index = activityLevelsAr.indexOf(level); + if (index >= 0 && index < activityLevelsEn.length) { + _selectedActivityLevel = activityLevelsEn[index]; + } + } else { + _selectedActivityLevel = level; + } notifyListeners(); } void setNumberOfReminders(String number) { - _selectedNumberOfReminders = number; + // Map Arabic values to English keys + if (getIt.get().isArabic()) { + final index = reminderOptionsAr.indexOf(number); + if (index >= 0 && index < reminderOptionsEn.length) { + _selectedNumberOfReminders = reminderOptionsEn[index]; + } + } else { + _selectedNumberOfReminders = number; + } notifyListeners(); } diff --git a/lib/features/weather/models/waether_cities_model.dart b/lib/features/weather/models/waether_cities_model.dart new file mode 100644 index 00000000..6a434bff --- /dev/null +++ b/lib/features/weather/models/waether_cities_model.dart @@ -0,0 +1,257 @@ +import 'dart:convert'; + +class GetCityInfoList { + CategoriesNames? categoriesNames; + String? category; + String? categoryValue; + int? cityId; + String? cityName; + String? cityNameN; + ColorName? colorName; + String? createdOn; + String? id; + int? iniceId; + bool? isOrderEmpty; + bool? isValuesReversed; + bool? language; + double? latitude; + double? longitude; + String? name; + int? orderNum; + double? temperature; + String? value; + + GetCityInfoList({ + this.categoriesNames, + this.category, + this.categoryValue, + this.cityId, + this.cityName, + this.cityNameN, + this.colorName, + this.createdOn, + this.id, + this.iniceId, + this.isOrderEmpty, + this.isValuesReversed, + this.language, + this.latitude, + this.longitude, + this.name, + this.orderNum, + this.temperature, + this.value, + }); + + factory GetCityInfoList.fromRawJson(String str) => GetCityInfoList.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory GetCityInfoList.fromJson(Map json) { + ColorName? parseColorName(dynamic value) { + if (value == null) return null; + try { + return colorNameValues.map[value.toString()]; + } catch (e) { + print("Unknown color name: $value"); + return null; + } + } + + CategoriesNames? parseCategoriesNames(dynamic value) { + if (value == null) return null; + try { + return CategoriesNames.fromJson(value as Map); + } catch (e) { + print("Error parsing CategoriesNames: $e"); + return null; + } + } + + return GetCityInfoList( + categoriesNames: parseCategoriesNames(json["CategoriesNames"]), + category: json["Category"], + categoryValue: json["CategoryValue"], + cityId: json["CityID"], + cityName: json["CityName"], + cityNameN: json["CityNameN"], + colorName: parseColorName(json["ColorName"]), + createdOn: json["CreatedOn"], + id: json["ID"], + iniceId: json["IniceID"], + isOrderEmpty: json["IsOrderEmpty"], + isValuesReversed: json["IsValuesReversed"], + language: json["Language"], + latitude: json["Latitude"]?.toDouble(), + longitude: json["Longitude"]?.toDouble(), + name: json["Name"], + orderNum: json["OrderNum"], + temperature: json["Temperature"]?.toDouble(), + value: json["Value"], + ); + } + + Map toJson() => { + "CategoriesNames": categoriesNames?.toJson(), + "Category": category, + "CategoryValue": categoryValue, + "CityID": cityId, + "CityName": cityName, + "CityNameN": cityNameN, + "ColorName": colorNameValues.reverse[colorName], + "CreatedOn": createdOn, + "ID": id, + "IniceID": iniceId, + "IsOrderEmpty": isOrderEmpty, + "IsValuesReversed": isValuesReversed, + "Language": language, + "Latitude": latitude, + "Longitude": longitude, + "Name": name, + "OrderNum": orderNum, + "Temperature": temperature, + "Value": value, + }; +} + +class CategoriesNames { + Category1? category1; + Category2? category2; + Category3? category3; + Category4? category4; + Category5? category5; + + CategoriesNames({ + this.category1, + this.category2, + this.category3, + this.category4, + this.category5, + }); + + factory CategoriesNames.fromRawJson(String str) => CategoriesNames.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory CategoriesNames.fromJson(Map json) { + T? parseEnum(dynamic value, EnumValues enumValues, String enumName) { + if (value == null) return null; + try { + return enumValues.map[value.toString()]; + } catch (e) { + print("Unknown $enumName value: $value"); + return null; + } + } + + return CategoriesNames( + category1: parseEnum(json["Category1"], category1Values, "Category1"), + category2: parseEnum(json["Category2"], category2Values, "Category2"), + category3: parseEnum(json["Category3"], category3Values, "Category3"), + category4: parseEnum(json["Category4"], category4Values, "Category4"), + category5: parseEnum(json["Category5"], category5Values, "Category5"), + ); + } + + Map toJson() => { + "Category1": category1Values.reverse[category1], + "Category2": category2Values.reverse[category2], + "Category3": category3Values.reverse[category3], + "Category4": category4Values.reverse[category4], + "Category5": category5Values.reverse[category5], + }; +} + +enum Category1 { + AT_EXTREME_RISK, + CATEGORY1_EXTREME, + EXTREME, + POOR +} + +final category1Values = EnumValues({ + "At Extreme Risk": Category1.AT_EXTREME_RISK, + "Extreme\u000d\n": Category1.CATEGORY1_EXTREME, + "Extreme": Category1.EXTREME, + "Poor": Category1.POOR +}); + +enum Category2 { + AT_HIGH_RISK, + CATEGORY2_VERY_HIGH, + FAIR, + VERY_HIGH +} + +final category2Values = EnumValues({ + "At High Risk": Category2.AT_HIGH_RISK, + "Very High\u000d\n": Category2.CATEGORY2_VERY_HIGH, + "Fair": Category2.FAIR, + "Very High": Category2.VERY_HIGH +}); + +enum Category3 { + AT_RISK, + CATEGORY3_HIGH, + GOOD, + HIGH +} + +final category3Values = EnumValues({ + "At Risk": Category3.AT_RISK, + "High\u000d\n": Category3.CATEGORY3_HIGH, + "Good": Category3.GOOD, + "High": Category3.HIGH +}); + +enum Category4 { + CATEGORY4_MODERATE, + MODERATE, + NEUTRAL, + VERY_GOOD +} + +final category4Values = EnumValues({ + "Moderate\u000d\n": Category4.CATEGORY4_MODERATE, + "Moderate": Category4.MODERATE, + "Neutral": Category4.NEUTRAL, + "Very Good": Category4.VERY_GOOD +}); + +enum Category5 { + BENEFICIAL, + CATEGORY5_LOW, + EXCELLENT, + LOW +} + +final category5Values = EnumValues({ + "Beneficial": Category5.BENEFICIAL, + "Low\u000d\n": Category5.CATEGORY5_LOW, + "Excellent": Category5.EXCELLENT, + "Low": Category5.LOW +}); + +enum ColorName { + GREEN, + ORANGE, + RED +} + +final colorNameValues = EnumValues({ + "green": ColorName.GREEN, + "orange": ColorName.ORANGE, + "red": ColorName.RED +}); + +class EnumValues { + Map map; + late Map reverseMap; + + EnumValues(this.map); + + Map get reverse { + reverseMap = map.map((k, v) => MapEntry(v, k)); + return reverseMap; + } +} diff --git a/lib/features/weather/weather_repo.dart b/lib/features/weather/weather_repo.dart new file mode 100644 index 00000000..26bf0a32 --- /dev/null +++ b/lib/features/weather/weather_repo.dart @@ -0,0 +1,72 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/core/location_util.dart'; +import 'package:hmg_patient_app_new/features/weather/models/waether_cities_model.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class WeatherRepo { + Future>>> getCityInfo(); +} + +class WeatherRepoImp implements WeatherRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + WeatherRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future>>> getCityInfo() async { + Map request = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + WEATHER_INDICATOR, + body: request, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + loggerService.logInfo("Weather API Response: $response"); + final list = response['GetCityInfo_List']; + + if (list == null || list.isEmpty) { + loggerService.logInfo("Weather API returned empty or null list"); + // Return empty list if no city info + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: [], + ); + return; + } + + loggerService.logInfo("Parsing ${list.length} city info items"); + final cityInfoList = list.map((item) => GetCityInfoList.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: cityInfoList, + ); + } catch (e) { + loggerService.logError("Error parsing weather data: $e"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } +} diff --git a/lib/features/weather/weather_view_model.dart b/lib/features/weather/weather_view_model.dart new file mode 100644 index 00000000..32b97753 --- /dev/null +++ b/lib/features/weather/weather_view_model.dart @@ -0,0 +1,102 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/location_util.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/weather/models/waether_cities_model.dart'; +import 'package:hmg_patient_app_new/features/weather/weather_repo.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:permission_handler/permission_handler.dart' show openAppSettings; + +import '../../core/app_state.dart' show AppState; + +class WeatherMonitorViewModel extends ChangeNotifier { + WeatherRepo weatherRepo; + ErrorHandlerService errorHandlerService; + LocationUtils locationUtils; + bool isLocationAvailable = false; + + WeatherMonitorViewModel({required this.weatherRepo, required this.errorHandlerService, required this.locationUtils}); + + final List _cityInfoList = []; + bool isLoading = false; + + List get cityInfoList => _cityInfoList; + + Future fetchCityInfoList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + isLoading = true; + notifyListeners(); + _cityInfoList.clear(); + locationUtils.isShowConfirmDialog = true; + locationUtils.getLocation( + isShowConfirmDialog: true, + onFailure: (){ + isLoading = false; + notifyListeners(); + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(), + getIt.get().navigatorKey.currentContext!, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.grantLocationPermission.tr(), + isShowActionButtons: true, + onCancelTap: () { + getIt.get().pop(); + }, + onConfirmTap: () async { + getIt.get().pop(); + openAppSettings(); + }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + onSuccess: (position) async { + isLocationAvailable = true; + final result = await weatherRepo.getCityInfo(); + result.fold( + (failure) async { + isLoading = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isLoading = false; + if (apiResponse.messageStatus == 2) { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? "Unknown error"); + } + } else if (apiResponse.messageStatus == 1) { + final cities = apiResponse.data ?? []; + _cityInfoList.addAll(cities); + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + }); + } + + Future checkIfTheLocationIsEnabledOrNot() async { + bool value = await locationUtils.isLocationPermissionEnabled(); + isLocationAvailable = value; + notifyListeners(); + return value; + + } + + void initiateFetchWeather() async { + if(await checkIfTheLocationIsEnabledOrNot()){ + fetchCityInfoList(); + } + } +} diff --git a/lib/generated/codegen_loader.g.dart b/lib/generated/codegen_loader.g.dart deleted file mode 100644 index 17950a46..00000000 --- a/lib/generated/codegen_loader.g.dart +++ /dev/null @@ -1,3442 +0,0 @@ -// DO NOT EDIT. This is code generated via package:easy_localization/generate.dart - -// ignore_for_file: prefer_single_quotes, avoid_renaming_method_parameters, constant_identifier_names - -import 'dart:ui'; - -import 'package:easy_localization/easy_localization.dart' show AssetLoader; - -class CodegenLoader extends AssetLoader{ - const CodegenLoader(); - - @override - Future?> load(String path, Locale locale) { - return Future.value(mapLocales[locale.toString()]); - } - - static const Map _ar_SA = { - "english": "الإنجليزية", - "arabic": "العربية", - "login": "تسجيل الدخول", - "noDataAvailable": "لا توجد بيانات متاحة", - "noRatingAvailable": "لا يوجد تقييم متاح", - "doctorDoesNotHaveRating": "لم يحصل هذا الطبيب على أي تقييمات بعد.", - "confirm": "تأكيد", - "loadingText": "جاري التحميل، يرجى الانتظار...", - "kilometerUnit": "كم", - "dashboardScreenToolbarTitle": "الرئيسية", - "settings": "الإعدادات", - "language": "لغة التطبيق", - "lanEnglish": "الإنجليزية", - "lanArabic": "العربية", - "cancel": "إلغاء", - "done": "تم", - "replay2": "إعادة التشغيل", - "home": "الرئيسية", - "services": "الخدمات", - "mySchedule": "جدولي", - "logout": "تسجيل الخروج", - "respirationRate": "معدل التنفس", - "bookAppo": "حجز موعد", - "searchBy": "البحث بواسطة:", - "clinic": "عيادة", - "byClinic": "حسب العيادة", - "hospital": "مستشفى", - "byHospital": "حسب المستشفى", - "name": "الاسم", - "doctor": "طبيب", - "clinicName": "اسم العيادة", - "hospitalName": "اسم المستشفى", - "noClinicFound": "لم يتم العثور على عيادة", - "doctorName": "اسم الطبيب", - "nearestAppo": "أقرب موعد", - "searchByDocText": "اكتب اسم الطبيب لمساعدتك في العثور عليه", - "enterDocName": "أدخل اسم الطبيب", - "search": "بحث", - "noResultFound": "لم يتم العثور على نتائج", - "noSearchResultFound": "لم يتم العثور على نتائج", - "pleaseEnterProductName": "يرجى إدخال اسم المنتج", - "bookNow": "احجز الآن", - "docInfo": "معلومات الطبيب", - "appoInfo": "معلومات الموعد", - "availableAppo": "المواعيد المتاحة", - "gender": "الجنس", - "nationality": "الجنسية", - "neverWait": "لا تنتظر في الطابور أبدًا", - "reminderTitle": "لديك موعد مع الدكتور.", - "appointmentCalendar": "هل ترغب في تعيين تذكير لهذا الموعد في التقويم الخاص بك؟", - "appointmentSuccess": "تم حجز الموعد بنجاح", - "docQualifications": "مؤهلات الطبيب", - "pressAgain": "اضغط مرة أخرى للخروج من التطبيق", - "laserMaxLimitReach": "الحد الأقصى هو 90 دقيقة", - "confirmAppoHeading": "يرجى مراجعة موعدك", - "patientInfo": "معلومات المريض", - "doctorFilter": "سيتم تصفية الأطباء بناءً على جنسك وعمرك", - "bookSuccess": "تم الحجز بنجاح", - "patientShare": "نسبة المريض", - "patientShareWithTax": "نسبة المريض مع الضريبة", - "confirmAppo": "تأكيد الموعد", - "confirmLiveCare": "تأكيد ال لايف كير", - "waitingForDoctor": "في انتظار الطبيب", - "appointment": "موعد", - "confirmLater": "تأكيد لاحقًا", - "todoList": "قائمة المهام", - "appoActions": "إجراءات الموعد", - "date": "التاريخ", - "time": "الوقت", - "pendingPayment": "معلق للدفع", - "payNow": "ادفع الآن", - "viewQR": "عرض رمز الاستجابة السريعة", - "instruction": "تعليمات", - "livecare": "لايف كير", - "livecareAppo": "موعد لايف كير", - "agreeTo": "أوافق على", - "termsConditoins": "الشروط والأحكام", - "cancelAppoMsg": "هل أنت متأكد أنك تريد إلغاء هذا الموعد؟", - "changePayment": "تغيير طريقة الدفع", - "upcomingNoAction": "لا يتطلب أي إجراء", - "upcomingConfirm": "يرجى تأكيد الموعد لتجنب الإلغاء", - "bookSuccessConfirmMore2412": "ستكون عملية الدفع عبر الإنترنت متاحة قبل 24 ساعة ��ن الموعد.", - "upcomingPaymentPending": "سيتم تفعيل الدفع عبر الإنترنت قبل 24 ساعة من موعد الموعد", - "upcomingPaymentNow": "ادفع عبر الإنترنت الآن لتجنب الانتظار الطويل", - "upcomingQR": "استخدم رمز الاستجابة السريعة لتسجيل الوصول في المستشفى", - "upcomingVirtual": "هذا موعد افتراضي، يرجى الالتزام بالتعليمات لتجربة سلسة.", - "upcomingLivecare": "هذا موعد لايف كير", - "upcomingDetails": "المزيد من التفاصيل", - "reschedule": "إعادة جدولة", - "raise": "رفع", - "complaint": "شكوى", - "add": "إضافة", - "reminder": "تذكير", - "location": "المواقع", - "online": "عبر الإنترنت", - "payment": "الدفع", - "onlinePayment": "عبر الإنترنت", - "onlinePaymentSubtitle": "الدفع", - "cancelNocaps": "إلغاء", - "setReminder": "ضبط تذكير", - "nationalIdNumber": "رقم الهوية الوطنية", - "medicalFileNumber": "رقم الملف الطبي", - "loginregister": "تسجيل الدخول / التسجيل", - "poweredBy": "مدعوم من", - "welcome": "مرحبًا", - "welcomeToDrSulaiman": "مرحباً بكم في مجموعة الدكتور سليمان الحبيب الطبية", - "patientApp": "تطبيق المريض", - "welcomeText": "تطبيق الدكتور سليمان الحبيب للهاتف المحمول", - "drSulaimanText": "الدكتور سليمان الحبيب", - "welcomeText2": "هل زرت مستشفيات أو مراكز الدكتور سليمان الحبيب من قبل؟", - "yes": "نعم", - "no": "لا", - "logintyperadio": "اختر من الخيارات أدناه لتسجيل الدخول إلى ملفك الطبي.", - "registernow": "سجل الآن", - "nationalID": "أدخل رقم الهوية", - "nationalId": "الهوية الوطنية", - "fileNo": "رقم الملف", - "fileno": "رقم الملف", - "forgotFileNo": "نسيت رقم الملف؟", - "forgotFileNoTitle": "نسيت الملف الطبي", - "enterNationalId": "يرجى إدخال رقم الهاتف المحمول ورقم الهوية", - "profileInfo": "يرجى إدخال معلومات الملف الشخصي", - "submit": "إرسال", - "forgotDesc": "أدخل رقم الهاتف المحمول لاستلام رقم الملف الطبي عبر الرسائل القصيرة", - "dob": "تاريخ الميلاد:", - "hijriDate": "الت��ريخ الهجري", - "gregorianDate": "التاريخ الميلادي", - "verifyLoginWith": "يرجى اختيار واحدة من الخيارات التالية للتحقق", - "registerUser": "تسجيل", - "verifyWithFingerprint": "البيومترية", - "verifyWithFaceid": "معرف الوجه", - "verifyWithSms": "رسالة قصيرة", - "verifyWithWhatsapp": "واتساب", - "verifyThrough": "التحقق من خلال", - "lastLogin": "آخر تسجيل دخول في:", - "lastLoginWith": "نوع التحقق:", - "verifyFingerprint": "لتفعيل خدمة تسجيل الدخول ببصمة الإصبع، يرجى التحقق من البيانات باستخدام واحدة من الخيارات التالية.", - "searchMedicine": "ابحث عن دواء", - "pharmaciesList": "قائمة الصيدليات", - "searchMedicineHere": "ابحث عن دواء هنا", - "description": "الوصف", - "howToUse": "كيفية الاستخدام", - "price": "السعر", - "youCanFindItIn": "يمكنك العثور عليه في", - "pleaseEnterMedicineName": "يرجى إدخال اسم الدواء", - "verificationMessage": "يرجى إدخال رمز التحقق المرسل إلى", - "validationMessage": "ينتهي رمز التحقق في", - "notification": "الإشعارات", - "appSettings": "إعدادات التطبيق", - "rateApp": "قيم تطبيقنا", - "before": "قبل", - "minute": "دقائق", - "hour": "ساعة", - "reminderSuccess": "تمت إضافة التذكير بنجاح", - "reminderCancelSuccess": "تم إلغاء التذكير بنجاح", - "patientShareToDo": "المبلغ قبل الضريبة: ", - "patientTaxToDo": "قيمة الضريبة: ", - "patientShareTotalToDo": "إجمالي المبلغ المستحق: ", - "paymentMethod": "طريقة الدفع", - "noNeedToWaitInLine": "لا حاجة للانتظار في الطابور.", - "useQRAppoAttend": "استخدم رمز الاستجابة السريعة لتسجيل حضور الموعد.", - "passQRAppoAttend": "مرر رمز الاستجابة السريعة عبر أجهزة الحضور المتاحة في المستشفى.", - "sitWaitingQR": "اجلس في غرف الانتظار حتى يتم استدعاؤك من قبل الممرضة.", - "attendRegisterCode": "رمز تسجيل الحضور", - "scanQRHospital": "توجّه إلى لوحة تسجيل الوصول الإلكتروني في المستشفى، وقم بالمسح عبر تقنية NFC أو رمز الاستجابة السريعة (QR) لإتمام تسجيل الوصول.", - "scanNFC": "امسح NFC لتسجيل الوصول", - "sendEmail": "إرسال بريد إلكتروني", - "success": "تم بنجاح", - "emailSentSuccessfully": "تم إرسال البريد الإلكتروني بنجاح", - "emailSentError": "خطأ في إرسال البريد الإلكتروني", - "close": "إغلاق", - "closeIt": "أغلق", - "booked": "محجوز", - "confirmed": "مؤكد", - "arrived": "وصل", - "payNowBookSuccess": "ادفع الآن عبر تطبيق الحبيب", - "payNowBookSuccesstext1": "ادفع الآن باستخدام خدمة الدفع عبر الإنترنت من بوابات الدفع الآمنة", - "payNowBookSuccesstext2": "يمكنك أيضًا الدفع لاحقًا عبر الدفع الإلكتروني أو في المستشفى.", - "payLater": "ادفع لاحقًا", - "askDocNotAllowed": "هذه الخدمة ستكون متاحة فقط لزيارة الطبيب قبل 15 يومًا", - "moreVerify": "خيارات تحقق إضافية", - "welcomeBack": "مرحبًا بعودتك!", - "accountInfo": "هل ترغب في تسجيل الدخول باستخدام اسم المستخدم الحالي؟", - "anotherAcc": "استخدم حسابًا آخر", - "next": "التالي", - "firstName": "الاسم الأول", - "middleName": "اسم الأب", - "lastName": "اسم العائلة", - "female": "أنثى", - "male": "ذكر", - "preferredLanguage": "اللغة المفضلة", - "locationsRegister": "أين ترغب في إنشاء هذا الملف؟", - "ksa": "السعودية", - "dubai": "دبي", - "enterEmail": "يرجى إدخال البريد الإلكتروني", - "family": "عائلتي", - "familyTitle": "ملفات عائلتي", - "myFamily": "عائلتي", - "addNewMember": "إضافة فرد جديد للعائلة", - "sentRequests": "الطلبات المرسلة", - "recievedRequests": "الطلبات المستلمة", - "manageFiles": "إدارة ملفات العائلة", - "oxygenation": "تشبع الأكسجين", - "bodyMeasurements": "مؤشر كتلة الجسم", - "temperature": "درجة الحرارة", - "pulse": "النبض", - "respiration": "التنفس", - "bloodPressure": "ضغط الدم", - "painScale": "مقياس الألم", - "weight": "الوزن", - "height": "الطول", - "heart": "معدل ضربات القلب", - "heightUnit": "وحدة الطول", - "weightUnit": "وحدة الوزن", - "unit": "الوحدة", - "request": "طلب", - "memberName": "اسم العضو", - "switchLogin": "تبديل", - "removeMember": "إزالة", - "allowView": "السماح", - "rejectView": "رفض", - "deleteView": "حذف", - "approvals": "الموافقات", - "approvalNo": "رقم الموافقة:", - "companyName": "اسم الشركة:", - "receiptOn": "الإيصال على:", - "expiryDate": "تاريخ الانتهاء:", - "expiryPoints": "منتهي الصلاحية", - "expiryOn": "ينتهي في:", - "procedureName": "اسم الإجراء:", - "procedure": "الإجراء", - "procedureStatus": "حالة الإجراء: ", - "usageStatus": "حالة الاستخدام", - "unusedCount": "عدد غير المستخدم:", - "totalApproval": "إجمالي الموافقة غير المستخدمة", - "category": "الفئة: ", - "expirationDate": "تاريخ انتهاء الصلاحية: ", - "patientCard": "رقم بطاقة المريض: ", - "policyNumber": "رقم الوثيقة: ", - "seeDetails": "عرض التفاصيل", - "insuranceCards": "بطاقات التأمين", - "requestType": "نوع الطلب", - "registerInfoFamily": "كيف تود إضافة عضو جديد؟", - "removeFamilyMember": "هل تريد إزالة هذا العضو؟", - "myMedicalFile": "ملفي الطبي", - "myMedicalFileSubTitle": "جميع سجلاتك الطبية", - "viewMore": "عرض المزيد", - "homeHealthCareService": "خدمة الرعاية الصحية المنزلية", - "onlinePharmacy": "الصيدلية الإلكترونية", - "emergencyService": "خدمة الطوارئ", - "onlinePaymentService": "خدمة الدفع عبر الإنترنت", - "offersAndPackages": "طلب تحويل عبر الإنترنت", - "comprehensiveMedicalCheckup": "فحص طبي شامل", - "hmgService": "خدمة HMG", - "viewAllHabibMedicalService": "عرض جميع خدمات الحبيب الطبية", - "viewAll": "عرض الكل", - "view": "عرض", - "contactUs": "اتصل بنا", - "contactUsLocation": "ص.ب: 91877 - الرياض 11643، طريق الملك فهد - العليا - المملكة العربية السعودية", - "contactUsTime": "السبت - الأربعاء 8:00 صباحًا - 10 مساءً، الخميس 8:00 صباحًا- 8:00 مساءً، الجمعة 2:00 مساءً - 8:00 مساءً", - "viewAllWaysReachUs": "عرض جميع طرق التواصل معنا", - "medicalProfile": "الملف الطبي", - "consultation": "استشارة", - "logs": "السجلات", - "textToSpeech": "كيف يمكنني مساعدتك؟", - "locationDialogMessage": "السماح لتطبيق HMG بالوصول إلى موقعك سيساعدك في عرض المستشفيات الأقرب إليك.", - "userViewRequester": "يريد المستخدم عرض ملفك الطبي", - "userView": "يمكن للمستخدم عرض ملفك الطبي", - "parking": "موقف السيارات", - "alhabiServices": "خدمة HMG", - "parkingTitle": "خدمة السيارة، خدمة السيارة، خدمة لحفظ معلومات الموقف، العودة إليها لاحقًا، 1- من خلال النقر على (قراءة الرمز)، حفظ بيانات الموقف. 2- من خلال النقر على زر (عرض موقف سيارتي)، يظهر لك موقع السيارة في خرائط Google. 3- قراءة موضع آخر بالضغط على زر مسح بيانات الموضع.", - "parkingDescription": "خدمة المواقف هي لك لمسح موقع السيارة حتى تتمكن من العثور عليها بسهولة عند مغادرتك. انقر على زر (مسح الموقف) لحفظ موقع الموقف، ثم سيظهر زر (عرض موقفي) لإظهار الطريق إلى الموقف. إذا كنت ترغب في إعادة مسح موقف السيارة QR فقط انقر على (مسح بياناتي).", - "appointments": "المواعيد", - "all2": "الكل", - "checkinOption": "تسجيل الوصول", - "readBarcode": "قراءة الكود", - "showMyPark": "عرض الموقف", - "clearMyData": "امسح البيانات", - "floor": "الطابق:", - "gate": "البوابة:", - "building": "المبنى:", - "branch": "الفرع:", - "emergencyServices": "خدمات الطوارئ", - "nearester": "أقرب طوارئ", - "locationa": "الموقع", - "callNow": "اتصل الآن", - "ambulancerequest": "طلب إسعاف", - "requestA": "طلب", - "noBookedAppointments": "لا توجد مواعيد محجوزة", - "noConfirmedAppointments": "لا توجد مواعيد مؤكدة", - "noArrivedAppointments": "لا توجد مواعيد تم تسجيل الوصول إليها", - "myAppointmentsList": "المواعيد", - "myAppointments": "مواعيدي", - "radiology": "الأشعة", - "radiologySubtitle": "النتيجة", - "lab": "المختبر", - "labSubtitle": "النتائج", - "medicines": "الأدوية", - "medicinesSubtitle": "الوصفات", - "vitalSigns": "علامات حيوية", - "vitalSignsSubTitle": "التقارير", - "vitalSignNormal": "طبيعي", - "vitalSignLow": "منخفض", - "vitalSignHigh": "مرتفع", - "vitalSignObese": "بدين", - "vitalSignOverweight": "زيادة الوزن", - "vitalSignUnderweight": "نقص الوزن", - "myMedical": "نشط", - "myMedicalSubtitle": "الأدوية", - "myDoctor": "أطبائي", - "myDoctorSubtitle": "القائمة", - "eye": "العين", - "eyeSubtitle": "القياس", - "insurance": "التأمين", - "insuranceSubtitle": "البطاقة", - "updateInsurance": "تحديث", - "updateInsuranceSubtitle": "التأمين", - "insuranceApproval": "التأمين", - "insuranceApprovalSubtitle": "الموافقات", - "allergies": "الحساسية", - "allergiesSubtitle": "القائمة", - "myVaccines": "لقاحاتي", - "myVaccinesSubtitle": "القائمة", - "medical": "طبي", - "medicalSubtitle": "التقرير", - "monthly": "شهري", - "monthlySubtitle": "التقرير", - "sick": "مريض", - "sickSubtitle": "الإجازات", - "myBalance": "رصيدي", - "myBalanceSubtitle": "الرصيد", - "patientCall": "مكالمة المريض", - "patientCallSubtitle": "الخدمة", - "smartWatches": "الساعات الذكية", - "smartWatchesSubtitle": "الاقتران", - "myTrackers": "متعقباتي", - "myTrackersSubtitle": "الخدمة", - "askYour": "اسأل", - "askYourSubtitle": "طبيبك", - "internet": "الإنترنت", - "internetSubtitle": "الاقتران", - "chatbot": "الدرد��ة الآلية", - "chatbotSubtitle": "", - "timeLine": "الخط الزمني", - "labOrders": "طلبات المختبر", - "billNo": "رقم الفاتورة:", - "prescriptions": "الوصفات", - "history": "التاريخ", - "orderNo": "رقم الطلب", - "orderDetails": "تفاصيل الطلب", - "deliveryDriverTrack": "تتبع السائق", - "deliveryLocation": "موقع التسليم", - "driver": "السائق", - "vitalSign": "علامة حيوية", - "monthlyReports": "تقارير شهرية", - "km": "كم:", - "km_": "كم", - "patientHealthSummaryReport": "تقرير ملخص صحة المريض", - "toViewTheTermsAndConditions": "لعرض الشروط والأحكام", - "clickHere": "انقر هنا", - "iAgreeToTheTermsAndConditions": "أوافق على الشروط والأحكام ", - "iAgreeToTheTermsAndConditionsSubtitle": "أوافق على الشروط والأحكام ", - "save": "حفظ", - "userAgreement": "اتفاقية المستخدم", - "updateSuccessfully": "تم التحديث بنجاح", - "checkVaccineAvailability": "تحقق من توفر اللقاح", - "myVaccinesAvailability": "توفر اللقاحات الخاصة بي", - "paymentService": "خدمة الدفع", - "paymentOnline": "الخدمة", - "onlineCheckIn": "تسجيل الوصول عبر الإنترنت", - "myBalances": "رصيدي", - "myWallet": "محفظتي", - "balanceAmount": "مبلغ المحفظة", - "totalBalance": "إجمالي الرصيد", - "createAdvancedPayment": "إعادة شحن المحفظة", - "advancePayment": "دفع مسبق", - "advancePaymentLabel": "يمكنك إنشاء وإضافة دفعة مقدمة لحسابك أو حسابات أخرى.", - "fileNumber": "رقم الملف", - "amount": "المبلغ *", - "depositorEmail": "البريد الإلكتروني للودائع *", - "notes": "ملاحظات", - "selectPatientName": "اختر اسم المريض", - "selectFamilyPatientName": "أفراد العائلة", - "selectHospital": "اختر المستشفى", - "selectCity": "اختر المدينة", - "city": "المدينة", - "distance": "المسافة", - "myAccount": "حسابي", - "otherAccount": "حساب آخر", - "selectBeneficiary": "اختر المستفيد", - "confirmThePayment": "تأكيد الدفع", - "depositorName": "اسم مُقدِّم الإيداع", - "mobileNumber": "رقم الجوال", - "phoneNumber": "رقم الهاتف", - "country": "البلد", - "ok": "حسنا", - "averageWaitingTime": "متوسط وقت الانتظار:", - "waitingTime": "وقت الانتظار المتوقع:", - "waterConsumedInWeek": "الماء المستهلك في أسبوع", - "waterConsumedInMonth": "الماء المستهلك في شهر", - "theVerificationCodeExpiresIn": "ينتهي رمز التحقق في", - "pleaseEnterTheVerificationCode": "يرجى إدخال رمز التحقق المرسل إلى", - "eyeMeasurements": "قياسات العين", - "measurements": "قياسات", - "classes": "نظارات", - "contactLens": "عدسات لاصقة", - "rightEye": "العين اليمنى", - "sphere": "الكرة", - "cylinder": "الأسطوانة", - "axis": "المحور", - "prism": "الهرم", - "va": "VA", - "leftEye": "العين اليسرى", - "brand": "العلامة التجارية", - "power": "القوة", - "diameter": "القطر", - "remarks": "ملاحظات: ", - "activeMedications": "الأدوية النشطة", - "expDate": "تاريخ انتهاء الصلاحية:", - "route": "الطريق", - "frequency": "التكرار", - "dailyQuantity": "��لكمية اليومية:", - "addReminder": "إضافة تذكير", - "cancelReminder": "إلغاء التذكير", - "reminderDes": "يرجى تحديد يوم ووقت بدء العلاج ليتم إعلامك عندما يحين وقت تناول الدواء", - "pleaseSelectAllQuestionToContinue": "يرجى الإجابة على جميع الأسئلة للمتابعة...", - "startDay": "يوم البدء", - "endDay": "يوم الانتهاء", - "days": "أيام ", - "pleaseVerify": "يرجى التحقق ", - "scheduleTime": "وقت الجدولة", - "askDoctor": "اسأل الطبيب", - "doctorResponses": "ردود الطبيب", - "ne_w": "جديد", - "all": "الكل", - "dailyWater": "متابعة الماء اليومية", - "questionHere": "أدخل السؤال هنا...", - "viewDoctorResponses": "عرض ردود الطبيب", - "serviceInformationButton": "تسجيل الدخول / التسجيل", - "serviceInformationTitle": "معلومات الخدمة", - "serviceInformation": "معلومات الخدمة", - "homeHealthCare": "الرعاية الصحية المنزلية", - "noAppointmentAvailable": "لا توجد مواعيد متاحة", - "loginRegister": "تسجيل الدخول / التسجيل", - "orderLog": "سجل الطلب", - "infoLab": "تتيح لك هذه الخدمة عرض نتائج جميع الفحوصات المخبرية التي أجريت في مجموعة الحبيب الطبية بالإضافة إلى إرسال التقرير عبر البريد الإلكتروني.", - "infoRadiology": "تتيح لك هذه الخدمة عرض تقارير وصور الأشعة في مجموعة الحبيب الطبية بالإضافة إلى إرسال التقرير عبر البريد الإلكتروني.", - "noReviewsAvailable": "لا توجد تقييمات متاحة", - "noLocationAvailable": "لا توجد مواقع متاحة", - "orders": "الطلبات", - "lakum": "لكم", - "lakumMsg": "لا توجد تفاصيل عن النقاط", - "lakumPoint": "نقاط لكم", - "wishlist": "قائمة الرغبات", - "products": "المنتجات", - "reviews": "التعليقات", - "brands": "العلامات التجارية", - "productDetails": "تفاصيل المنتج", - "medicationRefill": "إعادة تعبئة الدواء", - "pillReminder": "تذكير الأدوية", - "shippingAddresses": "عناوين الشحن", - "reachUs": "تواصل معنا", - "ourLocations": "مواقعنا", - "edit": "تعديل", - "whatsApp": "واتساب", - "phone": "الهاتف", - "delete": "حذف", - "deleteAddress": "هل أنت متأكد أنك تريد الحذف", - "deletedAddres": "تم حذف العنوان", - "addAddress": "إضافة عنوان جديد", - "addNewAddress": "إضافة عنوان جديد", - "order": "طلبي", - "delivered": "تم التوصيل", - "pending": "قيد الانتظار", - "enterNameHere": "أدخل اسمك هنا", - "processing": "جاري المعالجة", - "cancelled": "ملغى", - "writeReview": "اكتب تقييمًا", - "shareReview": "شارك التقييم", - "review": " التقييمات", - "viewMedicalFile": "عرض التفاصيل", - "viewAllServices": "عرض جميع الخدمات", - "medicalFile": "الملف الطبي", - "verified": "تم التحقق", - "checkup": "فحص", - "hhcHome": "الرئيسية", - "refferal": "إحالة إلكترونية", - "refferalTitle": "إحالة إلكترونية", - "refferalSubTitle": "الخدمة", - "healthCare": "الرعاية الصحية", - "emergency": "حالة طوارئ", - "erservices": "خدمات الطوارئ", - "services2": "الخدمات", - "cantSeeProfile": "لرؤية ملفك الطبي، يرجى تسجيل الدخول أو التسجيل الآن", - "loginRegisterNow": "تسجيل الدخول أو التسجيل الآن", - "hmgPharmacy": "صيدلية الحبيب", - "ecommerceSolution": "حلول التجارة الإلكترونية", - "comprehensive": "شامل", - "onlineConsulting": "استشارات عبر الإنترنت", - "pendingOrder": " قيد الانتظار", - "deliveredOrder": " تم التوصيل", - "processingOrder": " جارٍ المعالجة", - "cancelledOrder": " ملغى", - "compare": " قارن", - "medicationsRefill": " إعادة تعبئة الأدوية", - "recommended": " موصى به لك", - "myPrescription": " وصفتي", - "quantity": " الكمية ", - "reviewAppointment": " مراجعة الموعد ", - "backMyAccount": "العودة إلى حسابي ", - "reviewSuccessful": "تمت مراجعة الطلب بنجاح", - "reviewShared": "تمت مشاركة تقييمك في قسم تقييم المنتج", - "reviewComment": "تساعد تقييماتك الآخرين على اختيار منتج أفضل", - "shippedMethod": "الشحن بواسطة:", - "orderDetail": "تفاصيل الطلب", - "orderSummary": "ملخص الطلب", - "subtotal": "المجموع الفرعي", - "shipping": "الشحن", - "shipBy": "الشحن بواسطة:", - "lakumPoints": "نقاط لكم", - "useLakumPoints": "استخدم نقاط لكم", - "use": "استخدم", - "proceedPay": "المتابعة للدفع", - "vat": "ضريبة القيمة المضافة (15%)", - "inclusiveVat": "(شاملة ضريبة القيمة المضافة)", - "items": "عنصر(عناصر)", - "checkOut": "الدفع", - "sar": "ريال سعودي", - "aed": "درهم إماراتي", - "payOnline": "الدفع عبر الإنترنت", - "cancelOrder": "إلغاء الطلب", - "confirmAddress": "تأكيد العنوان ", - "confirmLocation": "أكد الموقع ", - "conditionsHMG": "الشروط والأحكام ", - "conditions": "الشروط والأحكام لكوم", - "confirmDeleteMsg": "هل أنت متأكد! تريد الحذف ", - "confirmDelete": "حذف", - "confirmCancellation": "هل أنت متأكد! تريد إلغاء هذا الطلب ", - "orderNumber": "رقم الطلب: ", - "orderDate": "التاريخ", - "itemsNo": "عنصر(عناصر)", - "noOrder": "ليس لديك أي طلبات.", - "noResult": "لا توجد نتيجة.", - "nonRecommended": "لا توجد منتجات موصى بها.", - "termsService": "شروط الخدمة", - "beforeusing": "قبل استخدام الفحص، يرجى قراءة شروط الخدمة.", - "accept": "أقرأ وأقبل شروط الخدمة وسياسة الخصوصية", - "dataSafeInfo": "المعلومات التي تقدمها مجهولة الهوية وليست مشتركة مع أي شخص.", - "dataSafe": " بياناتك آمنة.", - "informational": "الفحص لأغراض معلوماتية وليس رأيًا طبيًا مؤهلاً", - "notUseInEmerbency": "لا تستخدم في حالات الطوارئ.", - "notUseInEmerbencyDetails": "في حالة الطوارئ الصحية، ", - "notUseInEmerbencyDetailsCall": "اتصل بأقرب رقم طوارئ على الفور", - "checkDiagnosis": "الفحص ليس تشخيصًا.", - "remeberthat": "تذكر أن", - "loginToUseService": "تحتاج إلى تسجيل الدخول لاستخدام هذه الخدمة", - "offersAndPromotions": "عروض وترويجيات خاصة", - "offers": "عروض", - "myPrescriptions": "وصفاتي", - "searchAndScanMedication": "البحث والمسح الضوئي للدواء", - "shopByBrands": "تسوق حسب العلامات التجارية", - "recentlyViewed": "تمت مشاهدته مؤخرًا", - "bestSellers": "الأكثر مبيعًا", - "deleteAllItems": "حذف جميع العناصر", - "total": "الإجمالي", - "totalWithColonRight": "الإجمالي:", - "selectAddress": "اختر العنوان", - "shippingAddress": "عنوان الشحن", - "changeAddress": "تغيير العنوان", - "selectPaymentOption": "اختر طريقة الدفع", - "selectTamaraPlan": "اختر خطة دفع تمارا", - "changeMethod": "تغيير الطريقة", - "reviewOrder": "مراجعة الطلب", - "active": "نشط", - "inactive": "غير نشط", - "balance": "الرصيد", - "gained": "المكتسب", - "consumed": "المستهلك", - "transferred": "المحول", - "riyal": "ريال", - "membersince": "عضو منذ", - "identification": "رقم الهوية", - "lakumMobile": "رقم الجوال", - "waitinggained": "انتظار المكتسب", - "expired": "منتهي", - "willBeExpired": "سينتهي", - "activateLAKUMAccount": "تفعيل حساب لكم", - "checkBeneficiary": "تحقق من المستفيد", - "beneficiaryName": "اسم المستفيد", - "accountActivation": "تفعيل الحساب", - "lakumTransfer": "تحويل لكم", - "acceptLbl": "قبول", - "declineLbl": "رفض", - "selectGender": "اختر الجنس", - "iAmA": "أنا ...", - "selectAge": "اختر عمرك", - "select": "اختر", - "iAm": "أنا", - "yearsOld": "سنوات", - "dragPoint": "اسحب النقطة لتغيير عمرك", - "refine": "تنقيح", - "subGroup": "مجموعة فرعية", - "max": "الحد الأقصى", - "compeleteOrderMsg": "تم تقديم الطلب بنجاح!!", - "addToCompareMsg": "لقد أضفت منتجًا إلى قائمة المقارنة", - "itInListMsg": "العنصر موجود بالفعل في القائمة", - "compareListFull": "قائمة المقارنة ممتلئة", - "addQuantity": "يجب عليك إضافة الكمية", - "addToCartMsg": "لقد أضفت منتجًا إلى عربة التسوق", - "addToWishlistMsg": "لقد أضفت منتجًا إلى قائمة الرغبات", - "notifyMeMsg": "سوف يتم إبلاغك عند توفر المنتج", - "removeFromWishlistMsg": "لقد أزلت منتجًا من قائمة الرغبات", - "min": "الحد الأدنى", - "reset": "إعادة تعيين", - "apply": "تطبيق", - "viewCategorise": "عرض جميع الفئات", - "viewSubCategorise": "عرض جميع الفئات الفرعية", - "categorise": "الفئات", - "wishList": "قائمة الرغبات", - "cart": "عربة التسوق", - "alhabibapp": "تطبيق الحبيب", - "searchProductHere": "ابحث عن المنتج هنا", - "hhcNotAuthMsg": "تقدم هذه الخدمة مجموعة من خدمات الرعاية الصحية المنزلية، والمتابعة المستمرة والشاملة في أماكن إقامتهم لأولئك الذين لا يمكنهم الوصول إلى المرافق الصحية، مثل (تحليلات المختبر - الأشعة - التطعيمات - العلاج الطبيعي)، إلخ.", - "email": "البريد الإلكتروني *", - "book": "احجز", - "appointmentLabel": "موعد", - "bloodType": "فصيلة الدم", - "maritalStatus": "الحالة الاجتماعية", - "general": "عام", - "profile": "ملفي", - "notifications": "إشعارات", - "notificationDetails": "تفاصيل الإشعار", - "notificationDetailsa": "تفاصيل الإشعار", - "infoMyDoctorPoints": "", - "infoMyDoctor": "تتيح لك هذه الخدمة رؤية جميع الأطباء الذين زرتهم في مجموعة الحبيب الطبية، ومن خلال هذه الخدمة:", - "infoPrescriptions": "تتيح لك هذه الخدمة عرض جميع الوصفات الطبية التي أصدرتها مجموعة الحبيب الطبية، ومن خلال هذه الخدمة، يمكنك:", - "infoMyPrescriptionPoints": "", - "infoInsuranceCards": "تتيح لك هذه الخدمة عرض جميع بطاقات التأمين التي تم تسجيلها خلال زياراتك لمجموعة الحبيب الطبية بالإضافة إلى:", - "infoInsuranceCardsPoints": "", - "infoAllergies": "تتيح لك هذه الخدمة عرض جميع أنواع الحساسية المسجلة خلال زياراتك لمجموعة الحبيب الطبية.", - "infoSickLeaves": "تتيح لك هذه الخدمة عرض جميع الاجازات المرضيه التي تم أخذها في مجموعة الحبيب الطبية بالإضافة إلى:", - "infoSickLeavePoints": "", - "infoApprovals": "تتيح لك هذه الخدمة عرض جميع طلبات الموافقة التي تم إرسالها إلى شركات التأمين بالإضافة إلى:", - "infoApprovalPoints": "", - "infoMonthReport": "عند تفعيل هذه الخدمة، سيقوم النظام بإرسال تقرير شهري تلقائيًا إلى البريد الإلكتروني المسجل يتضمن العلامات الحيوية والنتائج لآخر الزيارات التي تمت في مجموعة الحبيب الطبية.", - "languageSetting": "الرسائل القصيرة، واتساب، لغة مكالمات التأكيد", - "alert": "تنبيهات", - "emailAlert": "تنبيه عبر البريد الإلكتروني", - "smsAlert": "تنبيه عبر الرسائل القصيرة", - "contactInfo": "معلومات الاتصال", - "emrgName": "اسم جهة الاتصال في حالات الطوارئ", - "emrgNo": "رقم جهة الاتصال في حالات الطوارئ", - "modes": "الأوضاع", - "vibration": "اهتزاز عند اللمس", - "blindModes": "أوضاع للمكفوفين جزئيًا", - "invertTheme": "عكس", - "offTheme": "إيقاف", - "dimTheme": "خافت", - "bwTheme": "أسود وأبيض", - "permissions": "إذن", - "cameraPermission": "الكاميرا", - "locationPermission": "الموقع", - "needPrescription": "يتطلب هذا المنتج وصفة طبية", - "outOfStockMsg": "لقد أضفت منتجًا غير متوفر حاليًا، يرجى إزالته!", - "noArabicLetters": "لا يُسمح باستخدام الحروف العربية", - "noOffersAvailable": "لا توجد عروض متاحة!", - "accessibility": "وضع الوصول", - "orderStatus": "حالة الطلب", - "findUs": "اعثر علينا", - "liveChat": "دردشة مباشرة", - "service": "خدمة", - "hmgServiceLabel": "خدمة مجموعة الحبيب الطبية", - "healthWeatherIndicators": "مؤشرات الطقس الصحية", - "healthTipsBasedOnCurrentWeather": "نصائح صحية بناءً على الطقس الحالي", - "moreDetails": "المزيد من التفاصيل", - "resendOrder": "إعادة التعبئة والتوصيل", - "ports": "المنافذ", - "way": "الطريقة", - "dailyDoses": "الجرعات اليومية", - "period": "الفترة", - "duration": "المدة", - "cm": "سم", - "ft": "قدم", - "kg": "كجم", - "lb": "رطل", - "birthDate": "تاريخ الميلاد", - "dateOfBirth": "تاريخ الميلاد", - "mass": "الكتلة", - "tempC": "°م", - "bpm": "نبضة في الدقيقة", - "respirationSigns": "التنفس", - "sysDias": "SBP/DBP", - "body": "مؤشر كتلة الجسم", - "bodyString": "الجسم", - "face": "وجه", - "retouch": "روتوش", - "bikini": "بيكيني", - "totalMinutes": "إجمالي الدقائق", - "feedback": "رأيك يهمنا", - "likeToHear": "نود سماع ملاحظاتك، ومخاوفك بشأن خدمات الرعاية الصحية وتجربة الخدمات الإلكترونية. يرجى استخدام النموذج أدناه", - "emptySubject": "يرجى إدخال الموضوع", - "emptyMessage": "يرجى إدخال الرسالة", - "selectAttachment": "اختر المرفق", - "complainAppo": "شكوى بخصوص الموعد", - "complainAppoAlt": "شكوى بدون موعد", - "messageType": "نوع الرسالة", - "feedbackType": "نوع الرسالة", - "compliment": "تقدير", - "suggestion": "اقتراح", - "yourFeedback": "تم إرسال رأيك", - "selectPart": "يرجى اختيار الجزء الذي تشتكي منه", - "number": "رقم", - "notClassified": "غير مصنف", - "selectClinic": "اختر العيادة", - "selectProject": "اختر المستشفى", - "searchItemError": "يجب أن يكون اسم العنصر أكثر من 3 أحرف", - "youCanFind": "يمكنك العثور على ", - "itemInSearch": " عنصر في البحث", - "bloodDonation": "التبرع بالدم", - "bloodDonationInfo": "من خلال هذه الخدمة، يمكنك تسجيل اسمك كمتبرع بالدم حيث سيتواصل معك بنك الدم في مجموعة الحبيب الطبية في حالة الحاجة إلى فصيلة الدم.", - "bloodInstruction": "أدخل المعلومات المطلوبة، من أجل التسجيل في خدمة التبرع بالدم", - "aboutBloodDonation": "عن التبرع بالدم", - "bloodDonationDescription": "التبرع بالدم هو عمل نبيل ينقذ الأرواح. كل تبرع واحد يمكن أن ينقذ ما يصل إلى ثلاثة أشخاص. من خلال التبرع بالدم، فإنك تساعد المرضى الذين يحتاجون إلى عمليات نقل دم، وحالات الطوارئ، والعلاجات الطبية.", - "bloodDonationBenefit1": "عملية آمنة وسريعة (10-15 دقيقة)", - "bloodDonationBenefit2": "يمكنك التبرع كل 8-12 أسبوع", - "bloodDonationBenefit3": "فحص صحي مجاني لدمك", - "viewTerms": "لعرض الشروط والأحكام", - "wantConnectHmgNetwork": "عزيزي العميل، لا يوجد وصول إلى الإنترنت، هل ترغب في الاتصال بشبكة مجموعة الحبيب الطبية لاستخدام تطبيقنا، تأكد من أنك في نطاق شبكة مجموعة الحبيب الطبية", - "failedToAccessHmgServices": "متصل بشبكة مجموعة الحبيب الطبية،\n\nلكن فشل في الوصول إلى خدمات مجموعة الحبيب الطبية", - "offerAndPackages": "عروض وباقات", - "offerAndPackagesDetails": "تتيح لك هذه الخدمة عرض جميع عروض مجموعة الحبيب الطبية:", - "invoiceNo": "رقم الفاتورة", - "invoiceDate": "تاريخ الفاتورة", - "specialResult": "نتيجة خاصة", - "generalResult": "نتيجة عامة", - "showMoreBtn": "مخطط التدفق", - "value": "القيمة", - "report": "تقرير الأشعة", - "openRad": "فتح الصورة", - "sendCopy": "إرسال التقرير عبر البريد الإلكتروني", - "appoSurvey": "استطلاع", - "appoSurveySubtitle": "استطلاع", - "labResults": "نتائج المختبر", - "doctorRating": "تقييم الطبيب", - "good": "جيد", - "vGood": "جيد جدًا", - "excellent": "ممتاز", - "average": "متوسط", - "infoSigns": "تتيح لك هذه الخدمة عرض جميع العلامات الحيوية التي تم قياسها في مجموعة الحبيب الطبية، مثل (الطول، الوزن، مؤشر كتلة الجسم، معدل ضربات القلب، إلخ.) بالإضافة إلى عرض بعض الرسوم البيانية الإحصائية.", - "infoAdvancePayment": "تم تصميم هذه الخدمة بحيث يمكنك إيداع مبلغ مقدم إما في حسابك أو في حساب شخص آخر، في مجموعة الحبيب الطبية.", - "infoMyBalance": "تتيح لك هذه الخدمة التحقق من رصيدك في جميع الفروع", - "erContant": "تظهر هذه الخدمة أقرب فرع من بين جميع فروع مجموعة الحبيب الطبية بناءً على موقعك الحالي.", - "infoAmbulance": "من خلال هذه الخدمة، يمكنك طلب الإخلاء بواسطة سيارة إسعاف، سواء من المنزل أو إلى المنزل، بالإضافة إلى مجموعة من الخدمات الأخرى", - "infoMyAppointments": "تتيح لك هذه الخدمة رؤية جميع المواعيد التي زرتها في مجموعة الحبيب الطبية، ومن خلال هذه الخدمة:", - "infoTodo": "تم تصميم هذه الخدمة لتمكينك من الحصول على رابط سريع لقائمة المهام التي تحتاج القيام بها", - "familyInfo": "من خلال هذه الخدمة، ستتمكن من ربط ملفات عائلتك الطبية بملفك الطبي حتى تتمكن من إدارة سجلاتهم عن طريق تسجيل الدخول إلى ملفك الطبي.", - "rrtdDetails": "تقدم فريق الاستجابة السريعة خدمة طبية شاملة لجميع أنواع الحالات العاجلة والمستقرة من جميع الأعمار، بما في ذلك البالغين والمرضى الأطفال، وتغطي الخدمة فريق طبي مدرب تدريبًا عاليًا ومجهز تجهيزًا كاملاً قادرًا على تقديم أفضل رعاية طبية في منزل المريض.", - "homeHealthCareText": "تقدم هذه الخدمة مجموعة من خدمات الرعاية الصحية المنزلية، والمتابعة المستمرة والشاملة في أماكن إقامتهم لأولئك الذين لا يمكنهم الوصول إلى المرافق الصحية، مثل (تحليلات المختبر - الأشعة - التطعيمات - العلاج الطبيعي)، إلخ.", - "onlineCheckInAgreement": "تسجيل الوصول عبر الإنترنت مخصص للحالات التي لا تهدد الحياة. اتصل بالهلال الأحمر (997) أو اذهب إلى أقرب قسم طوارئ إذا كان هناك: \n\nعلامات السكتة الدماغية أو النوبة القلبية \nhالة من النوبات أو الإغماء \nهناك إصابة تهدد الطرف أو الحياة \nصورة لإصابات خطيرة", - "infoEreferral": "تتيح لك هذه الخدمة تقديم طلب إحالة من أي مقدم رعاية صحية سواء داخل أو خارج المملكة العربية السعودية إلى أي من مستشفيات HMG، عن طريق ملء بعض بيانات المريض وإرفاق التقارير الطبية، علاوة على ذلك يمكنك تتبع حالة الطلب (قيد المعالجة، مقبول أو مرفوض)", - "erConsultation": "تتيح لك هذه الخدمة إجراء استشارة افتراضية عبر الإنترنت عبر مكالمة فيديو مباشرة مع الطبيب من أي مكان وفي أي وقت.", - "myInvoice": "القائمة", - "invoiceList": "فواتيري", - "allInvoices": "كل الفواتير", - "hospitals": "المستشفيات", - "clinics": "العيادات", - "doctors": "الأطباء", - "selectDoctor": "اختر الطبيب", - "thisItemIsNotAvailable": "هذا العنصر غير متوفر", - "beforeAfterImages": "صور قبل وبعد", - "clinicAcceptLivecare": "لا حاجة للانتظار أو الزيارة يمكنك الآن الحصول على استشارة طبية عبر مكالمة فيديو (لايف كير) في اسم العيادة وسيتصل بك الطبيب على الفور", - "livecareModalTop": "تقبل هذه العيادة خدمات ال لايف كير", - "livecarePoint2": "سوف يتصل بك طبيب متخصص", - "livecarePoint3": "سيكون لدى الطبيب القدرة على رؤية تاريخ ملفك الطبي الكامل", - "livecarePoint4": "تتوفر خدمة توصيل الأدوية المجانية", - "livecarePoint5": "لا حاجة للزيارة، الخدمة في مكانك", - "bookImmediateLivecare": "أوافق على الحصول على الخدمة على الفور", - "bookVideoLivecare2": "لا أحتاج لزيارة العيادة", - "noThankyou": "لا شكرًا", - "visitClinic": "جدولة موعد مع الطبيب", - "generateCovidCertificate": "إصدار شهادة كوفيد-19", - "isReportOutsideKsa": "هل الشهادة مطلوبة خارج المملكة العربية السعودية؟", - "passportNumber": "رقم جواز السفر", - "enterPassportNumber": "يرجى تأكيد أو تحديث رقم جواز السفر الخاص بك:", - "validPassportNumber": "يرجى إدخال رقم جواز سفر صالح", - "continuePlan": "متابعة خطة العلاج؟", - "aboutApp": "حول التطبيق", - "dontHaveAccount": "ليس لديك حساب؟", - "loginOrRegister": "تسجيل الدخول أو التسجيل", - "myFiles": "ملفاتي", - "resultsPending": "النتائج معلقة", - "resultsAvailable": "النتائج متاحة", - "viewReport": "عرض التقرير", - "checkAvailability": "التحقق من التوفر", - "readInstructions": "قراءة التعليمات", - "searchLabReport": "ابحث عن تقرير المختبر", - "prescriptionDeliveryError": "هذه العيادة لا تدعم إعادة التعبئة والتسليم.", - "receiveOtpToast": "أين تود تلقي رمز التحقق OTP؟", - "enterPhoneNumber": "أدخل رقم الهاتف", - "enterEmailDesc": "أدخل عنوان بريدك الإلكتروني لإكمال عملية إنشاء ملف طبي", - "enterPhoneDesc": "أدخل رقم هاتفك لتلقي رمز التحقق ", - "pleaseChooseOption": "الرجاء اختيار من الخيارات أدناه لتلقي رمز التحقق OTP", - "prepareToElevate": "هل أنت مستعد لتحسين صحتك ورفاهيتك؟", - "iAcceptTermsConditions": "أوافق على الشروط والأحكام", - "alreadyHaveAccount": "هل لديك حساب بالفعل؟", - "loginNow": "تسجيل الدخول الآن", - "notice": "إشعار", - "oR": "أو", - "sendOTPWHATSAPP": "أرسل لي OTP عبر واتساب", - "sendOTPSMS": "أرسل لي OTP عبر الرسائل القصيرة", - "fullName": "الاسم الكامل", - "married": "متزوج", - "uae": "الإمارات العربية المتحدة", - "malE": "ذكر", - "loginBy": "تسجيل الدخول بواسطة", - "loginByOTP": "تسجيل الدخول بواسطة OTP", - "guest": "زائر", - "switchAccount": "تبديل الحساب", - "lastLoginBy": "آخر تسجيل دخول بواسطة", - "allSet": "جاهز! الآن يمكنك تسجيل الدخول باستخدام Face ID أو البصمة", - "enableQuickLogin": "تمكين تسجيل الدخول السريع", - "enableMsg": "تمكين تسجيل الدخول السريع سيسمح بالتحقق من خلال Face ID / Biometric الخاص بجهازك الحالي", - "notNow": "ليس الآن", - "pendingActivation": "في انتظار التنشيط", - "awaitingApproval": "انتظر القبول", - "news": "أخبار", - "ready": "جاهز", - "enterValidNationalId": "رقم الهوية أو رقم الملف غير صحيح", - "enterValidPhoneNumber": "الرجاء إدخال رقم هاتف صالح", - "cannotEnterSaudiOrUAENumber": "لا يمكنك إدخال أرقام هواتف السعودية (00966) أو الإمارات (00971) عند اختيار دولة 'أخرى'", - "medicalCentersWithCount": "{count} مراكز طبية", - "medicalCenters": "مراكز طبية", - "hospitalsWithCount": "{count} مستشفيات", - "selectRegion": "اختر المنطقة", - "selectFacility": "اختر المرافق", - "selectFacilitiesSubTitle": "يرجى اختيار المرفق للموعد", - "selectHospitalSubTitle": "يرجى اختيار المستشفى للموعد", - "iAcceptThe": "أوافق على", - "personalDetailsVerification": "التحقق من التفاصيل الشخصية", - "otpVerification": "التحقق من OTP", - "weHaveSendOTP": "لقد أرسلنا OTP إلى", - "via": "عبر", - "forRegistrationVerification": "للتحقق من التسجيل", - "didntReceiveIt": "لم تستلمه؟", - "resendOTP": "إعادة إرسال", - "resendIn": "إعادة الإرسال في", - "pleaseEnterAnationalID": "يرجى إدخال رقم الهوية الوطنية", - "pleaseEnterAFileNumber": "يرجى إدخال رقم الملف", - "pleaseEnterAValidEmail": "يرجى إدخال بريد إلكتروني صالح", - "pleaseEnterFullName": "يرجى إدخال الاسم الكامل", - "pleaseAcceptTermsConditions": "يرجى قبول الشروط والأحكام", - "pleaseEnterAValidIqamaID": "يرجى إدخال رقم إقامة صالح", - "pleaseEnterAValidNationalID": "يرجى إدخال رقم هوية وطنية صالح", - "pleaseEnterAValidDateOfBirth": "يرجى إدخال تاريخ ميلاد صالح", - "pleaseEnterAValidName": "يرجى إدخال اسم صالح", - "pleaseSelectAGender": "يرجى اختيار الجنس", - "pleaseSelectAMaritalStatus": "يرجى اختيار الحالة الاجتماعية", - "pleaseSelectACountry": "يرجى اختيار الدولة", - "pleaseEnterEmail": "يرجى إدخال البريد الإلكتروني", - "pleaseEnterAValidEmailFormat": "يرجى إدخال تنسيق بريد إلكتروني صالح", - "selectCountry": "اختر الدولة", - "forLoginVerification": "للتحقق من تسجيل الدخول", - "searchHospital": "بحث في المستشفى", - "skip": "تخطي", - "getStarted": "ابدأ الآن", - "onboardingHeading1": "حجز المواعيد لم يكن أسهل من الآن.", - "onboardingBody1": "ببضع نقرات فقط يمكنك استشارة الطبيب الذي تختاره.", - "onboardingHeading2": "الوصول إلى السجل الطبي بين يديك", - "onboardingBody2": "تتبع تاريخك الطبي بما في ذلك الفحوصات المخبرية، الوصفات الطبية، التأمين، وغيرها.", - "hmgHospitals": "مستشفيات الحبيب", - "hmcMedicalClinic": "مراكز مجموعة الحبيب الطبية", - "applyFilter": "تطبيق الفلتر", - "facilityAndLocation": "المرفق والموقع", - "regionAndLocation": "المنطقة والمواقع", - "clearAllFilters": "مسح جميع الفلاتر", - "filters": "فلاتر", - "searchClinic": "بحث عن عيادة", - "normal": "عادي", - "attention": "انتباه", - "monitor": "مراقبة", - "noSpecialResult": "لا توجد نتائج خاصة", - "setTheDateRange": "تعيين النطاق الزمني", - "historyFlowchart": "مخطط تدفق التاريخ", - "to": "إلى", - "startDate": "تاريخ البدء", - "endDate": "تاريخ الانتهاء", - "walkin": "زيارة بدون موعد", - "laserClinic": "عيادة الليزر", - "continueString": "يكمل", - "covid_info": "تجري مستشفيات د. سليمان الحبيب فحص فيروس كورونا المستجد وتصدر شهادات السفر على مدار الساعة، طوال أيام الأسبوع، وبسرعة ودقة عالية. يمكن للراغبين في الاستفادة من هذه الخدمة زيارة أحد فروع مستشفيات د. سليمان الحبيب وإجراء فحص كورونا خلال بضع دقائق والحصول على النتائج خلال عدة ساعات خدمة فحص فيروس كورونا Covid 19 بتقنية PCR للكشف عن الفيروس وفقاً لأعلى المعايير العالمية وبأحدث أجهزة RT-PCR عالية الدقة (GeneXpert الأمريكي وغيره)، وهي طرق معتمدة من قبل هيئة الغذاء والدواء وكذلك من قبل المركز السعودي للوقاية من الأمراض المُعدية", - "appointmentDetails": "تفاصيل الموعد", - "checkingDoctorAvailability": "جاري التحقق من توفر الطبيب...", - "cancellingAppointmentPleaseWait": "جاري إلغاء الموعد، يرجى الانتظار...", - "appointmentCancelledSuccessfully": "تم إلغاء الموعد بنجاح", - "notConfirmed": "غير مؤكد", - "appointmentStatus": "حالة الموعد", - "doctorWillCallYou": "سيتصل بك الطبيب عندما يقترب موعدك.", - "getDirections": "الحصول على الاتجاهات", - "notifyMeBeforeAppointment": "أبلغني قبل الموعد", - "fetchingLabResults": "جاري جلب نتائج المختبر...", - "fetchingRadiologyResults": "جاري جلب نتائج الأشعة...", - "fetchingAppointmentPrescriptions": "جاري جلب وصفات الموعد...", - "noPrescriptionsForAppointment": "ليس لديك أي وصفات طبية لهذا الموعد.", - "amountBeforeTax": "المبلغ قبل الضريبة", - "rebookAppointment": "إعادة حجز الموعد", - "fetchingDoctorSchedulePleaseWait": "جاري جلب جدول الطبيب، يرجى الانتظار...", - "pickADate": "اختر تاريخاً", - "confirmingAppointmentPleaseWait": "جاري تأكيد الموعد، يرجى الانتظار...", - "appointmentConfirmedSuccessfully": "تم تأكيد الموعد بنجاح", - "appointmentPayment": "دفع الموعد", - "checkingPaymentStatusPleaseWait": "جاري التحقق من حالة الدفع، يرجى الانتظار...", - "paymentFailedPleaseTryAgain": "فشل الدفع! يرجى المحاولة مرة أخرى.", - "appointmentCheckIn": "تسجيل حضور الموعد", - "insuranceExpiredOrInactive": "التأمين منتهي الصلاحية أو غير نشط", - "totalAmountToPay": "المبلغ الإجمالي المستحق", - "vat15": "ضريبة القيمة المضافة 15%", - "liveCare": "لايف كير", - "recentVisits": "الزيارات الأخيرة", - "favouriteDoctors": "الأطباء المفضلون", - "searchByClinic": "البحث حسب العيادة", - "tapToSelectClinic": "انقر لاختيار العيادة", - "searchByDoctor": "البحث حسب الطبيب", - "tapToSelect": "انقر للاختيار", - "searchByRegion": "البحث حسب المنطقة", - "centralRegion": "المنطقة الوسطى", - "immediateConsultation": "استشارة فورية", - "scheduledConsultation": "استشارة مجدولة", - "pharmaLiveCare": "لايف كير الصيدلية", - "notSureHelpMeChooseClinic": "غير متأكد؟ ساعدني في اختيار عيادة!", - "mentionYourSymptomsAndFindDoctors": "اذكر أعراضك واعثر على قائمة الأطباء وفقاً لذلك", - "immediateService": "خدمة فورية", - "noNeedToWaitGetMedicalConsultation": "لا حاجة للانتظار، ستحصل على استشارة طبية فورية عبر مكالمة فيديو", - "noVisitRequired": "لا حاجة للزيارة", - "doctorWillContact": "سيتصل بك الطبيب", - "specialisedDoctorWillContactYou": "سيتصل بك طبيب متخصص وسيكون قادراً على الاطلاع على تاريخك الطبي", - "freeMedicineDelivery": "توصيل مجاني للأدوية", - "offersFreeMedicineDelivery": "يوفر توصيل مجاني للأدوية لموعد لايف كير", - "dentalChiefComplaints": "الشكاوى الرئيسة للأسنان", - "viewAvailableAppointments": "عرض المواعيد المتاحة", - "doctorProfile": " ملف الطبيب", - "waitingAppointment": "موعد الانتظار", - "hospitalInformation": "معلومات المستشفى", - "fetchingAppointmentShare": "جاري جلب تفاصيل الموعد...", - "bookingYourAppointment": "جاري حجز موعدك...", - "selectLiveCareClinic": "عيادة لايف كير", - "checkingForExistingDentalPlan": "جاري التحقق من وجود خطة أسنان حالية، يرجى الانتظار...", - "dentalTreatmentPlan": "خطة علاج الأسنان", - "youHaveExistingTreatmentPlan": "لديك خطة علاج حالية: ", - "mins": "دقيقة", - "totalTimeRequired": "إجمالي الوقت المطلوب", - "wouldYouLikeToContinue": "هل تريد متابعتها؟", - "chooseDoctor": "اختر الطبيب", - "viewNearestAppos": "عرض أقرب المواعيد المتاحة", - "noDoctorFound": "لم يتم العثور على طبيب مطابق للمعايير المحددة...", - "yesPleasImInAHurry": "نعم من فضلك، أنا في عجلة من أمري", - "fetchingFeesPleaseWait": "جاري جلب الرسوم، يرجى الانتظار...", - "noThanksPhysicalVisit": "لا، شكراً. أفضل الزيارة الشخصية", - "offline": "غير متصل", - "videoCall": "مكالمة فيديو", - "liveVideoCallWithHMGDoctors": "مكالمة فيديو مباشرة مع أطباء مجموعة الحبيب الطبية", - "liveAudioCallWithHMGDoctors": "مكالمة صوتية مباشرة مع أطباء مجموعة الحبيب الطبية", - "audioCall": "مكالمة صوتية", - "phoneCall": "مكالمة هاتفية", - "livePhoneCallWithHMGDoctors": "مكالمة هاتفية مباشرة مع أطباء مجموعة الحبيب الطبية", - "reviewLiveCareRequest": "مراجعة طلب لايف كير", - "selectedLiveCareType": "نوع لايف كير المحدد", - "selectLiveCareCallType": "اختر نوع مكالمة لايف كير", - "confirmingLiveCareRequest": "جاري تأكيد طلب لايف كير، يرجى الانتظار...", - "unknownErrorOccurred": "حدث خطأ غير معروف...", - "liveCarePermissionsMessage": "يتطلب لايف كير أذونات الكاميرا والميكروفون والموقع والإشعارات لتمكين الاستشارة الافتراضية بين المريض والطبيب، يرجى السماح بهذه الأذونات للمتابعة.", - "liveCarePayment": "دفع لايف كير", - "mada": "مدى", - "visaOrMastercard": "فيزا أو ماستركارد", - "tamara": "تمارا", - "fetchingApplePayDetails": "جاري جلب تفاصيل Apple Pay، يرجى الانتظار...", - "liveCarePendingRequest": "طلب لايف كير قيد الانتظار", - "callLiveCareSupport": "اتصل بدعم لايف كير", - "whatIsWaitingAppointment": "ما هو موعد الانتظار؟", - "waitingAppointmentsFeature": "تتيح لك ميزة مواعيد الانتظار حجز موعد أثناء تواجدك داخل مبنى المستشفى، وفي حال عدم توفر مواعيد متاحة في جدول الطبيب.", - "appointmentWithDoctorConfirmed": "الموعد مع الطبيب مؤكد، ولكن وقت الدخول غير محدد.", - "paymentWithinTenMinutes": "ملاحظة: يجب عليك الدفع خلال 10 دقائق من الحجز، وإلا سيتم إلغاء موعدك تلقائياً", - "liveLocation": "الموقع المباشر", - "verifyYourLocationAtHospital": "تحقق من موقعك في المستشفى لتسجيل الحضور", - "error": "خطأ", - "ensureWithinHospitalLocation": "يرجى التأكد من أنك داخل موقع المستشفى لإجراء تسجيل الحضور عبر الإنترنت.", - "nfcNearFieldCommunication": "NFC (الاتصال قريب المدى)", - "scanPhoneViaNFC": "امسح هاتفك عبر لوحة NFC لتسجيل الحضور", - "qrCode": "رمز QR", - "scanQRCodeToCheckIn": "امسح رمز QR بالكاميرا لتسجيل الحضور", - "processingCheckIn": "جاري معالجة تسجيل الحضور...", - "bookingWaitingAppointment": "جاري حجز موعد الانتظار، يرجى الانتظار...", - "enterValidIDorIqama": "يرجى إدخال رقم هوية وطنية أو رقم ملف صالح", - "selectAppointment": "حدد الموعد", - "rebookSameDoctor": "أعد الحجز مع نفس الطبيب", - "queueing": "قائمة الانتظار", - "inQueue": "في قائمة الانتظار", - "yourTurn": "دورك", - "halaFirstName": "هلا {firstName}!!!", - "thankYouForPatience": "شكراً لصبرك، هذا هو رقم قائمة الانتظار الخاص بك.", - "servingNow": "يُخدم الآن", - "callForVitalSigns": "نداء للعلامات الحيوية", - "callForDoctor": "نداء للطبيب", - "thingsToAskDoctor": "أشياء تسأل طبيبك عنها اليوم", - "improveOverallHealth": "ماذا يمكنني أن أفعل لتحسين صحتي العامة؟", - "routineScreenings": "هل هناك أي فحوصات روتينية يجب أن أجريها؟", - "whatIsThisMedicationFor": "لماذا هذا الدواء؟", - "sideEffectsToKnow": "هل هناك أي آثار جانبية يجب أن أعرفها؟", - "whenFollowUp": "متى يجب أن أعود للمتابعة؟", - "goToHomepage": "الذهاب إلى الصفحة الرئيسية", - "appointmentsList": "قائمة المواعيد", - "allAppt": "جميع المواعيد", - "upcoming": "القادمة", - "completed": "المكتملة", - "noAppointmentsYet": "ليس لديك أي مواعيد بعد.", - "viewProfile": "عرض الملف الشخصي", - "choosePreferredHospitalForService": "اختر المستشفى المفضل لديك للخدمة", - "noHospitalsFound": "لم يتم العثور على مستشفيات", - "cancelOrderConfirmation": "هل أنت متأكد أنك تريد إلغاء هذا الطلب؟", - "orderCancelledSuccessfully": "تم إلغاء الطلب بنجاح", - "requestID": "معرف الطلب:", - "noCMCOrdersYet": "ليس لديك أي طلبات فحص شامل بعد.", - "cmcOrders": "طلبات الفحص الشامل", - "summary": "الملخص", - "selectedService": "الخدمة المحددة", - "requestSubmittedSuccessfully": "تم إرسال طلبك بنجاح.", - "hereIsYourRequestNumber": "هذا هو رقم طلبك #: ", - "pleaseSelectHospitalToContinue": "يرجى اختيار مستشفى للمتابعة", - "confirmSubmitRequest": "هل أنت متأكد أنك تريد إرسال هذا الطلب؟", - "pendingOrderWait": "لديك طلب معلق. يرجى الانتظار حتى تتم معالجته.", - "noServicesAvailable": "لا توجد خدمات متاحة", - "selectAService": "اختر خدمة", - "comprehensiveCheckup": "الفحص الشامل", - "viewNearestHMGLocations": "عرض أقرب مواقع مجموعة الحبيب الطبية", - "provideFeedbackOnServices": "قدم ملاحظاتك على خدماتنا", - "liveChatWithHMG": "خيار الدردشة المباشرة مع مجموعة الحبيب الطبية", - "send": "إرسال", - "status": "الحالة", - "sendingFeedback": "جاري إرسال الملاحظات...", - "selectFeedbackType": "اختر نوع الملاحظات", - "loadingAppointmentsList": "جاري تحميل قائمة المواعيد...", - "noAppointmentsForFeedback": "ليس لديك أي مواعيد لتقديم ملاحظات عنها.", - "selectedAppointment": "الموعد المحدد:", - "subject": "الموضوع", - "enterSubjectHere": "أدخل الموضوع هنا", - "message": "الرسالة", - "enterMessageHere": "أدخل الرسالة هنا", - "filesSelected": "تم تحديد {count} ملف(ات)", - "otherDetails": "تفاصيل أخرى", - "medicalReport": "التقرير الطبي", - "medicalReportNumber": "التقرير الطبي {number}", - "patientIsInsured": "المريض مؤمن عليه", - "insuranceDocument": "وثيقة التأمين", - "selectBranch": "اختر الفرع", - "patientInformation": "معلومات المريض", - "patientLocation": "أين يتواجد المريض", - "identificationNumber": "رقم الهوية", - "enterIdentificationNumber": "أدخل رقم الهوية*", - "patientName": "اسم المريض*", - "referralRequesterInformation": "معلومات مقدم طلب الإحالة", - "enterReferralRequesterName": "أدخل اسم مقدم طلب الإحالة*", - "requesterName": "اسم مقدم الطلب", - "relationship": "العلاقة", - "selectRelation": "اختر العلاقة", - "otherName": "اسم آخر", - "otherNameHint": "اسم آخر*", - "requesterNameRequired": "اسم مقدم طلب الإحالة مطلوب", - "selectRelationshipRequired": "يرجى اختيار العلاقة", - "otherRelationshipNameRequired": "اسم العلاقة الأخرى مطلوب", - "identificationNumberRequired": "رقم الهوية مطلوب", - "patientNameRequired": "اسم المريض مطلوب", - "enterPatientPhoneRequired": "يرجى إدخال رقم هاتف المريض", - "selectPatientCityRequired": "يرجى اختيار مدينة المريض", - "medicalReportRequired": "مطلوب تقرير طبي واحد على الأقل", - "selectBranchRequired": "يرجى اختيار الفرع", - "insuranceDocumentRequired": "وثيقة التأمين مطلوبة للمرضى المؤمن عليهم", - "searchResult": "نتيجة البحث", - "referralNo": "رقم الإحالة {number}", - "eReferral": "الإحالة الإلكترونية", - "referralCreatedSuccessfully": "تم إنشاء إحالتك بنجاح.", - "hereIsYourReferralNumber": "هذا هو رقم الإحالة الخاص بك #: ", - "searchEReferral": "البحث عن إحالة إلكترونية", - "enterRequiredInfoToSearch": "يرجى إدخال المعلومات المطلوبة للبحث عن إحالة إلكترونية", - "selectPickupDirection": "اختر اتجاه الاستلام", - "selectDirection": "اختر الاتجاه", - "toHospital": "إلى المستشفى", - "fromHospital": "من المستشفى", - "selectWay": "اختر الطريقة", - "oneWay": "اتجاه واحد", - "twoWay": "اتجاهين", - "selectPickupDetails": "اختر تفاصيل الاستلام", - "pleaseSelectDetailsOfPickup": " يرجى تحديد تفاصيل الاستلام", - "selectDetails": "اختر التفاصيل", - "work": "العمل", - "pick": "استلام", - "insideTheHome": "داخل المنزل", - "haveAnyAppointment": "هل لديك أي موعد", - "amountPaidAtHospital": "سيتم دفع المبلغ في المستشفى", - "submitRequest": "إرسال الطلب", - "enterPickupLocationManually": "أدخل موقع الاستلام يدوياً", - "enterPickupLocation": "أدخل موقع الاستلام", - "trackingDetails": "تفاصيل التتبع", - "cancelRequest": "إلغاء الطلب", - "shareLocationWhatsapp": "مشاركة موقعك المباشر على واتساب", - "pleaseWaitForCall": "يرجى انتظار المكالمة", - "toHospitalLower": "إلى المستشفى", - "contact": "اتصال", - "failed": "فشل", - "confirmationCall": "مكالمة التأكيد", - "pickupFromHome": "الاستلام من المنزل", - "onTheWayToHospital": " في الطريق إلى المستشفى", - "arrivedAtHospital": "وصل إلى المستشفى", - "orderCancel": "إلغاء الطلب", - "emergencyCheckIn": "تسجيل الطوارئ الإلكتروني", - "erOnlineCheckInDescription": "تتيح هذه الخدمة للمرضى تسجيل موعد الطوارئ قبل الوصول.", - "erOnlineCheckInSuccess": "تم تسجيل وصول الطوارئ بنجاح. الرجاء التوجه إلى منطقة الانتظار.", - "erOnlineCheckInError": "حدث خطأ غير متوقع أثناء عملية التسجيل. يرجى التواصل مع الدعم.", - "fetchingHospitalsList": "جاري جلب قائمة المستشفيات...", - "fetchingPaymentInformation": "جاري جلب معلومات الدفع...", - "erVisitDetails": "تفاصيل زيارة الطوارئ", - "erClinic": "عيادة الطوارئ", - "vatWithAmount": "الضريبة 15% ({amount})", - "erAppointmentBookedSuccess": "تم حجز موعدك بنجاح. يرجى إتمام إجراءات تسجيل الوصول عند وصولك إلى المستشفى.", - "underProcessing": "قيد المعالجة", - "canceledByPatient": "ملغى بواسطة المريض", - "rapidResponseTeam": "فريق الاستجابة السريع", - "allFacilities": "جميع المرافق", - "selectLocation": "اختر الموقع", - "pleaseSelectTheLocation": "يرجى اختيار الموقع", - "viewLocationGoogleMaps": "عرض الموقع على خرائط جوجل", - "callAmbulance": "استدعاء سيارة إسعاف", - "requestAmbulanceInEmergency": "طلب سيارة إسعاف في حالة الطوارئ من المنزل أو المستشفى", - "confirmation": "تأكيد", - "areYouSureYouWantToCallAmbulance": "هل أنت متأكد أنك تريد استدعاء سيارة إسعاف؟", - "getDetailsOfNearestBranch": "احصل على تفاصيل أقرب فرع بما في ذلك الاتجاهات", - "areYouSureYouWantToCallRRT": "هل أنت متأكد أنك تريد استدعاء فريق الاستجابة السريعة (RRT)؟", - "priorERCheckInToSkipLine": "تسجيل الوصول المسبق في الطوارئ لتجاوز الطابور والدفع في الاستقبال.", - "areYouSureYouWantToMakeERCheckIn": "هل أنت متأكد أنك تريد إجراء تسجيل وصول الطوارئ؟", - "checkingYourERAppointmentStatus": "جاري التحقق من حالة موعد الطوارئ الخاص بك...", - "transportOptions": "خيارات النقل", - "selectHospitalForAdvancePayment": "يرجى اختيار المستشفى الذي ترغب في دفع مبلغ مقدم له.", - "recharge": "إعادة الشحن", - "activityLevel": "مستوى النشاط", - "selectActivityLevel": "حدد مستوى النشاط", - "caloriesPerDay": "السعرات الحرارية في اليوم الواحد", - "dietType": "نوع النظام الغذائي", - "selectDietType": "حدد نوع النظام الغذائي", - "bodyFrameSize": "حجم إطار الجسم", - "selectBodyFrameSize": "حدد حجم إطار الجسم", - "averageCycleLength": "متوسط طول الدورة الشهرية (عادةً 28 يومًا)", - "averageLutealPhase": "متوسط طول المرحلة الأصفرية (عادةً 14 يومًا)", - "convert": "يتحول", - "calculate": "احسب", - "healthCalculators": "حاسبات الصحة", - "healthConverters": "محولات الصحة", - "generalHealth": "الصحة العامة", - "relatedToBMICalories": "متعلق بمؤشر كتلة الجسم والسعرات الحرارية ودهون الجسم وما إلى ذلك للبقاء على اطلاع دائم بصحتك.", - "selectCalculator": "اختر الآلة الحاسبة", - "womensHealth": "صحة المرأة", - "relatedToPeriodsOvulation": "متعلق بالدورة الشهرية والإباضة والحمل ومواضيع أخرى.", - "bloodSugar": "سكر الدم", - "trackYourGlucoseLevels": "تتبع مستويات الجلوكوز لديك، وفهم الاتجاهات، واحصل على رؤى مخصصة لصحة أفضل.", - "bloodCholesterol": "كوليسترول الدم", - "monitorCholesterolLevels": "راقب مستويات الكوليسترول، وقيّم مخاطر صحة القلب، واتخذ خطوات استباقية للرفاهية.", - "triglyceridesFatBlood": "الدهون الثلاثية في الدم", - "understandTriglyceridesImpact": "افهم تأثير الدهون الثلاثية على صحة القلب مع رؤى مخصصة وتوصيات الخبراء.", - "bmiCalculator": "حاسبة\nمؤشر كتلة الجسم", - "caloriesCalculator": "حاسبة\nالسعرات الحرارية", - "bmrCalculator": "حاسبة\nمعدل الأيض الأساسي", - "idealBodyWeight": "الوزن المثالي\nللجسم", - "bodyFatCalculator": "حاسبة\nدهون الجسم", - "carbsProteinFat": "الكربوهيدرات\nالبروتين والدهون", - "ovulationPeriod": "فترة\nالإباضة", - "deliveryDueDate": "تاريخ الولادة\nالمتوقع", - "low": "منخفض", - "preDiabetic": "ما قبل السكري", - "high": "مرتفع", - "elevated": "مرتفع قليلاً", - "recorded": "مسجل", - "noRecordsYet": "لا توجد سجلات بعد", - "lastRecord": "آخر سجل", - "addBloodSugar": "إضافة سكر الدم", - "addBloodPressure": "إضافة ضغط الدم", - "addWeight": "إضافة الوزن", - "bloodSugarDataSavedSuccessfully": "تم حفظ بيانات سكر الدم بنجاح", - "bloodPressureDataSavedSuccessfully": "تم حفظ بيانات ضغط الدم بنجاح", - "weightDataSavedSuccessfully": "تم حفظ بيانات الوزن بنجاح", - "pleaseWait": "يرجى الانتظار", - "selectUnit": "اختر الوحدة", - "selectMeasureTime": "اختر وقت القياس", - "selectArm": "اختر الذراع", - "enterBloodSugar": "أدخل سكر الدم", - "enterSystolicValue": "أدخل القيمة الانقباضية", - "enterDiastolicValue": "أدخل القيمة الانبساطية", - "enterWeight": "أدخل الوزن", - "selectDuration": "اختر المدة", - "systolic": "الانقباضي", - "diastolic": "الانبساطي", - "sendReportByEmail": "إرسال التقرير عبر البريد الإلكتروني", - "enterYourEmailToReceiveReport": "أدخل عنوان بريدك الإلكتروني لاستلام التقرير", - "addNewRecord": "إضافة سجل جديد", - "healthTrackers": "متتبعات الصحة", - "monitorBloodPressureLevels": "راقب مستويات ضغط الدم لديك، وتتبع القراءات الانقباضية والانبساطية، وحافظ على صحة قلبك.", - "trackWeightProgress": "تتبع تقدم وزنك، وضع الأهداف، وحافظ على كتلة جسم صحية من أجل العافية الشاملة.", - "bookAppointment": "احجز موعد", - "completeCheckup": "الفحص الشامل", - "indoorNavigation": "الملاحة الداخلية", - "eReferralServices": "خدمات الإحالة الإلكترونية", - "dailyWaterMonitor": "مراقب الماء اليومي", - "fetchingYourWaterIntakeDetails": "جاري جلب تفاصيل استهلاك الماء الخاص بك.", - "healthCalculatorsServices": "حاسبات\nالصحة", - "healthConvertersServices": "محولات\nالصحة", - "smartWatchesServices": "الساعات\nالذكية", - "exploreServices": "استكشف الخدمات", - "medicalAndCareServices": "الخدمات الطبية والرعاية", - "hmgServices": "خدمات مجموعة الحبيب الطبية", - "personalServices": "الخدمات الشخصية", - "habibWallet": "محفظة الحبيب", - "loginToViewWalletBalance": "سجل الدخول لعرض رصيد محفظتك", - "loginToViewMedicalFile": "سجل الدخول لعرض ملفك الطبي", - "addMember": "إضافة عضو", - "addFamilyMember": "إضافة فرد من العائلة", - "pleaseFillBelowFieldToAddNewFamilyMember": "يرجى ملء الحقل أدناه لإضافة فرد جديد من العائلة إلى ملفك الشخصي", - "healthTools": "أدوات الصحة", - "supportServices": "خدمات الدعم", - "virtualTour": "جولة افتراضية", - "carParking": "موقف السيارات", - "latestNews": "آخر الأخبار", - "hmgContact": "اتصل بمجموعة الحبيب الطبية", - "durationCannotExceed90": "لا يجوز أن تتجاوز المدة 90 دقيقة", - "unexpectedError": "حدث خطأ غير متوقع", - "gettingAmbulanceTransportOption": "جاري الحصول على خيارات نقل الإسعاف", - "fetchingAppointment": "جاري جلب الموعد", - "doYouWantToCancelTheRequest": "هل تريد إلغاء الطلب", - "cancellingRequest": "جاري إلغاء الطلب", - "fetchingTermsAndConditions": "جاري جلب الشروط والأحكام", - "selectLocationPrescriptionDelivery": "يرجى تحديد موقع توصيل الوصفة الطبية", - "noRadiologyOrders": "لم يتم العثور على أي طلبات تصوير شعاعي", - "ageIsRequired": "العمر مطلوب", - "invalidAge": "العمر غير صالح", - "ageMustBeBetween11And120": "يجب أن يكون العمر بين 11 و 120", - "heightIsRequired": "الطول مطلوب", - "invalidHeight": "الطول غير صالح", - "weightIsRequired": "الوزن مطلوب", - "invalidWeight": "الوزن غير صالح", - "timeToDrinkWater": "حان وقت شرب الماء! 💧", - "stayHydratedDrinkWater": "ابق رطبًا! اشرب {amount} مل من الماء.", - "visitPharmacyOnline": "زيارة الصيدلية على الانترنت", - "howAreYouFeelingToday": "كيف حالك اليوم؟", - "checkYourSymptomsWithScale": "تحقق من أعراضك باستخدام ذا المقياس", - "checkYourSymptoms": "تحقق من أعراضك", - "noUpcomingAppointmentPleaseBook": "ليس لديك أي مواعيد قادمة. يرجى حجز موعد", - "youHaveEROnlineCheckInRequest": "لديك طلب تسجيل وصول عبر الإنترنت للطوارئ", - "quickLinks": "روابط سريعة", - "viewMedicalFileLandingPage": "عرض الملف الطبي", - "immediateLiveCareRequest": "طلب لايف كير فوري", - "yourTurnIsAfterPatients": "دورك بعد {count} مريض. يرجى الانتظار حتى يحين دورك، وسيناديك الطبيب قريباً.", - "dontHaveHHCOrders": "ليس لديك أي أوامر رعاية صحية منزلية حتى الآن.", - "hhcOrders": "أوامر الرعاية الصحية المنزلية", - "requestedServices": "الخدمات المطلوبة", - "selectServices": "اختر الخدمات", - "selectedServices": "الخدمات المختارة", - "createNewRequest": "إنشاء طلب جديد", - "youHaveNoPendingRequests": "ليس لديك أي طلبات معلقة.", - "noInsuranceDataFound": "لم يتم العثور على بيانات التأمين...", - "noInsuranceUpdateRequest": "لم يتم العثور على أي طلبات لتحديث بيانات التأمين.", - "policyNumberInsurancePage": "الوثيقة: {number}", - "insuranceExpired": "التأمين منتهي الصلاحية", - "insuranceActive": "التأمين نشط", - "patientCardID": "رقم بطاقة المريض: {id}", - "noInsuranceApprovals": "لم تحصل على أي موافقات تأمينية حتى الآن.", - "noInsuranceWithHMG": "ليس لديك تأمين مسجل لدى مجموعة الحبيب الطبية.", - "referenceRange": "النطاق المرجعي", - "downloadReport": "تنزيل التقرير", - "generatingReport": "جارٍ إنشاء التقرير، يرجى الانتظار...", - "noLabResults": "ليس لديك أي نتائج مختبرية حتى الآن.", - "labResultDetails": "تفاصيل نتائج المختبر", - "resultOf": "نتيجة {date}", - "whatIsThisResult": "ما هي هذه النتيجة؟", - "lastTested": "آخر اختبار", - "byVisit": "حسب الزيارة", - "byTest": "حسب التحليل", - "results": "نتائج", - "viewResults": "عرض النتائج", - "rebook": "إعادة الحجز", - "noOphthalmologyAppointments": "لم يتم العثور على أي مواعيد في قسم طب العيون...", - "noVitalSignsRecordedYet": "لا توجد علامات حيوية مسجلة بعد", - "appointmentsAndVisits": "المواعيد والزيارات", - "labAndRadiology": "المختبر والأشعة", - "activeMedicationsAndPrescriptions": "الوصفات الطبية", - "allPrescriptions": "جميع الوصفات", - "allMedications": "جميع الأدوية", - "youDontHaveAnyPrescriptionsYet": "ليس لديك أي وصفات طبية بعد.", - "youDontHaveAnyCompletedVisitsYet": "ليس لديك أي زيارات مكتملة بعد", - "others": "أخرى", - "allergyInfo": "معلومات الحساسية", - "vaccineInfo": "معلومات اللقاحات", - "updateInsuranceInfo": "تحديث التأمين", - "myInvoicesList": "قائمة فواتيري", - "ancillaryOrdersList": "قائمة الطلبات المساعدة", - "youDontHaveAnySickLeavesYet": "ليس لديك أي إجازات مرضية بعد.", - "medicalReports": "التقارير الطبية", - "sickLeaveReport": "تقرير الإجازة المرضية", - "weightTracker": "متتبع الوزن", - "askYourDoctor": "اسأل طبيبك", - "internetPairing": "الاقتران بالإنترنت", - "requested": "مطلوب", - "youDontHaveAnyMedicalReportsYet": "ليس لديك أي تقارير طبية بعد.", - "requestMedicalReport": "طلب تقرير طبي", - "youDoNotHaveAnyAppointmentsToRequestMedicalReport": "ليس لديك أي مواعيد لطلب تقرير طبي.", - "areYouSureYouWantToRequestMedicalReport": "هل أنت متأكد أنك تريد طلب تقرير طبي لهذا الموعد؟", - "yourMedicalReportRequestSubmittedSuccessfully": "تم إرسال طلب التقرير الطبي بنجاح.", - "monthlyHealthSummaryReportDisclaimer": "يعكس تقرير الملخص الصحي الشهري هذا المؤشرات الصحية ونتائج التحليل لأحدث الزيارات. يرجى ملاحظة أن هذا سيتم إرساله تلقائيًا من النظام ولا يعتبر تقريرًا رسميًا لذا لا ينبغي اتخاذ أي قرار طبي بناءً عليه", - "updatingMonthlyReportStatus": "جاري تحديث حالة التقرير الشهري...", - "monthlyReportStatusUpdatedSuccessfully": "تم تحديث حالة التقرير الشهري بنجاح", - "whoCanViewMyMedicalFile": "من يمكنه عرض ملفي الطبي؟", - "acceptedYourRequestToBeYourFamilyMember": "{status} طلب الانضمام الى أفراد عائلتك", - "canViewYourFile": "يمكنه عرض ملفك", - "hasARequestPendingToBeYourFamilyMember": "لديه طلب {status} ليكون فردًا من عائلتك", - "wantsToAddYouAsTheirFamilyMember": "يرغب في إضافتك كفرد من أفراد العائلة", - "rejectedYourRequestToBeYourFamilyMember": "{status} طلب الانضمام إلى عائلتك", - "rejectedYourFamilyMemberRequest": "{status} طلب أحد أفراد عائلتك", - "notAvailable": "غير متاح", - "selectAProfile": "الرجاء تحديد ملف تعريف", - "switchFamilyFile": "قم بالتبديل من قائمة الملفات الطبية أدناه", - "medicalFiles": "الملفات الطبية", - "addANewFamilyMember": "إضافة فرد جديد من العائلة", - "viewInvoiceDetails": "عرض تفاصيل الفاتورة", - "outPatient": "مريض خارجي", - "invoiceDetails": "تفاصيل الفاتورة", - "sendingEmailPleaseWait": "جاري إرسال ال��ريد الإلكتروني، يرجى الانتظار...", - "emailSentSuccessfullyMessage": "تم إرسال البريد الإلكتروني بنجاح.", - "discount": "خصم", - "paid": "مدفوع", - "fetchingInvoiceDetails": "جارٍ جلب تفاصيل الفاتورة، يرجى الانتظار...", - "scanQRCode": "مسح رمز الاستجابة السريعة", - "parkingSlotDetails": "تفاصيل موقف السيارة", - "slotNumber": "رقم الموقف: {code}", - "basement": "الطابق: {description}", - "parkingDate": "التاريخ: {date}", - "parkedSince": "متوقف منذ: {time}", - "resetDirection": "إعادة تعيين الاتجاه", - "noPrescriptionOrdersYet": "ليس لديك أي طلبات وصفات طبية حتى الآن.", - "fetchingPrescriptionPDFPleaseWait": "جاري جلب ملف الوصفة الطبية، يرجى الانتظار...", - "ratingValue": "التقييم: {rating}", - "downloadPrescription": "تحميل الوصفة الطبية", - "fetchingPrescriptionDetails": "جاري جلب تفاصيل الوصفة الطبية...", - "switchBackFamilyFile": "العودة إلى ملف العائلة", - "profileAndSettings": "الملف الشخصي والإعدادات", - "quickActions": "إجراءات سريعة", - "notificationsSettings": "إعدادات الإشعارات", - "touchIDFaceIDServices": "خدمات Touch ID / Face ID", - "personalInformation": "المعلومات الشخصية", - "updateEmailAddress": "تحديث عنوان البريد الإلكتروني", - "helpAndSupport": "المساعدة والدعم", - "permissionsProfile": "الأذونات", - "privacyPolicy": "سياسة الخصوصية", - "deactivateAccount": "إلغاء تنشيط الحساب", - "ageYearsOld": "{age} {yearsOld}", - "youDontHaveRadiologyOrders": "ليس لديك أي نتائج للأشعة حتى الآن.", - "radiologyResult": "نتيجة الأشعة", - "viewRadiologyImage": "عرض صورة الأشعة", - "rateClinic": "تقييم العيادة", - "back": "رجوع", - "rateDoctor": "تقييم الطبيب", - "howWasYourLastVisitWithDoctor": "كيف كانت زيارتك الأخيرة مع الطبيب؟", - "dateOfBirthSymptoms": "ما هو تاريخ ميلادك؟", - "genderSymptoms": "ما هو جنسك؟", - "heightSymptoms": "كم طولك؟", - "weightSymptoms": "ما هو وزنك؟", - "femaleGender": "أنثى", - "previous": "سابق", - "selectedOrgans": "الهيئات المختارة", - "noOrgansSelected": "لم يتم تحديد أي أعضاء بعد", - "organSelector": "محدد الأعضاء", - "noPredictionsAvailable": "لا توجد تنبؤات متاحة", - "areYouSureYouWantToRestartOrganSelection": "هل أنت متأكد أنك تريد إعادة تشغيل اختيار الأعضاء؟", - "possibleConditions": "الحالات المحتملة", - "pleaseSelectAtLeastOneRiskBeforeProceeding": "يرجى اختيار عامل خطر واحد على الأقل قبل المتابعة", - "aboveYouSeeCommonRiskFactors": "أعلاه ترى عوامل الخطر الأكثر شيوعًا. على الرغم من أن /diagnosis قد تعيد أسئلة حول عوامل الخطر، ", - "readMore": "اقرأ المزيد", - "riskFactors": "عوامل الخطر", - "noRiskFactorsFound": "لم يتم العثور على عوامل خطر", - "basedOnYourSelectedSymptomsNoRiskFactors": "بناءً على الأعراض المحددة، لم يتم تحديد عوامل خطر إضافية.", - "messageNotification": "الرسالة", - "attachedImage": "الصورة المرفقة", - "failedToLoadImage": "فشل تحميل الصورة", - "typeNotification": "النوع", - "pleaseSelectAtLeastOneOptionBeforeProceeding": "يرجى اختيار خيار واحد على الأقل قبل المتابعة", - "suggestions": "الاقتراحات", - "pleaseGoBackAndSelectOrgansFirst": "يرجى العودة واختيار الأعضاء أولاً", - "symptomsSelector": "محدد الأعراض", - "possibleSymptomsRelatedTo": "الأعراض المحتملة المتعلقة بـ", - "suggestionAIListDescription": "هذه قائمة بالأعراض المقترحة من قبل الذكاء الاصطناعي، بناءً على المعلومات التي تم جمعها حتى الآن خلال المقابلة", - "analyzing": "جاري التحليل...", - "seekMedicalAdvice": "اطلب المشورة الطبية", - "monitorDescription": "لا حاجة لطلب المشورة الطبية. فقط حافظ على روتين صحي.", - "seekMedicalAdviceDescription": "ليست حالة طوارئ ولكن من الأفضل مراقبة الأعراض.", - "emergencyDescription": "تحتاج إلى استشارة الطبيب في أقرب وقت ممكن قبل فوات الأوان.", - "colorScience": "دلالات الألوان", - "noNeedToVisitDoctor": "لا حاجة لزيارة الطبيب", - "commonSymptom": "الأعراض الشائعة", - "notApplicable": "لا ينطبق", - "emergencyTriage": "طوارئ", - "emergencyEvidenceDetected": "تم اكتشاف دليل طوارئ. يرجى طلب العناية الطبية.", - "noQuestionItemsAvailable": "لا توجد عناصر أسئلة متاحة", - "pleaseAnswerAllQuestionsBeforeProceeding": "يرجى الإجابة على جميع الأسئلة قبل المتابعة", - "triage": "الفرز", - "areYouSureYouWantToExitProgress": "هل أنت متأكد أنك تريد الخروج؟ سيتم فقدان تقدمك.", - "noQuestionAvailable": "لا يوجد سؤال متاح", - "possibleSymptom": "عرض محتمل: ", - "symptomsCheckerFindingScore": "- درجة نتائج فاحص الأعراض", - "notSet": "غير محدد", - "years": "سنوات", - "symptomsChecker": "فاحص الأعراض", - "helloIsYourInformationUpToDate": "مرحباً {name}، هل معلوماتك محدثة؟", - "noEditAll": "لا، تعديل الكل", - "yesItIs": "نعم، إنها كذلك", - "age": "العمر", - "youDontHaveAnyAncillaryOrdersYet": "ليس لديك أي طلبات مساعدة بعد.", - "invoiceWithNumber": "الفاتورة: {invoiceNo}", - "queued": "في قائمة الانتظار", - "checkInReady": "جاهز للتسجيل", - "checkIn": "تسجيل الوصول", - "viewDetails": "عرض التفاصيل", - "selectPaymentMethod": "اختر طريقة الدفع", - "processingPaymentPleaseWait": "جاري معالجة الدفع، يرجى الانتظار...", - "finalizingPaymentPleaseWait": "جاري إتمام الدفع، يرجى الانتظار...", - "generatingInvoicePleaseWait": "جاري إنشاء الفاتورة، يرجى الانتظار...", - "hereIsYourInvoiceNumber": "هذا هو رقم فاتورتك #: ", - "paymentCompletedSuccessfully": "تم الدفع بنجاح", - "failedToInitializeApplePay": "فشل في تهيئة Apple Pay. يرجى المحاولة مرة أخرى.", - "cash": "نقدي", - "approved": "موافق عليه", - "approvalRejectedPleaseVisitReceptionist": "تم رفض الموافقة - يرجى زيارة موظف الاستقبال", - "sentForApproval": "تم إرساله للموافقة", - "ancillaryOrderDetails": "تفاصيل الطلبات المساندة", - "noProceduresAvailableForSelectedOrder": "لا توجد إجراءات متاحة للطلب المحدد.", - "procedures": "الإجراءات", - "totalAmount": "المبلغ الإجمالي", - "covered": "مغطى", - "vatPercent": "ضريبة القيمة المضافة (15%)", - "proceedToPayment": "المتابعة للدفع", - "supportedSmartWatches": "الساعات الذكية المدعومة", - "pleaseMakeSureSamsungWatchConnected": "يرجى التأكد من أن ساعة Samsung الخاصة بك متصلة بهاتفك، ومتزامنة ومحدثة بشكل نشط.", - "beforeSyncingDataFollowInstructions": "قبل مزامنة البيانات، يرجى التأكد من اتباع التعليمات بشكل صحيح.", - "viewWatchInstructions": "عرض تعليمات الساعة", - "healthConnectAppNotInstalled": "يبدو أنه ليس لديك تطبيق Health Connect مثبتًا. يرجى تثبيته من متجر Play لمزامنة بيانات صحتك.", - "setTimerOfReminder": "ضبط مؤقت التذكير", - "youHaveAppointmentWithDr": "لديك موعد مع د. ", - "hours": "ساعات", - "secs": "ثواني", - "noAllergiesDataFound": "لم يتم العثور على بيانات الحساسية...", - "heartRateDescription": "معدل ضربات قلبك يشير إلى عدد المرات التي ينبض فيها قلبك في الدقيقة", - "bloodOxygenDescription": "مستوى الأكسجين في الدم يشير إلى كمية الأكسجين التي تحملها خلايا الدم الحمراء", - "stepsDescription": "عدد الخطوات المتخذة على مدار اليوم", - "caloriesDescription": "السعرات الحرارية المحروقة أثناء النشاط البدني", - "distanceDescription": "المسافة المقطوعة على مدار اليوم", - "overview": "نظرة عامة", - "details": "التفاصيل", - "healthy": "صحي", - "warning": "تحذير", - "vitalSignDetails": "تفاصيل العلامات الحيوية", - "resultOfNoDate": "نتيجة --", - "referenceRangeBetween": "النطاق المرجعي: {low} – {high} {unit}", - "referenceRangeMin": "النطاق المرجعي: ≥ {low} {unit}", - "referenceRangeMax": "النطاق المرجعي: ≤ {high} {unit}", - "noHistoryAvailable": "لا يوجد تاريخ متاح", - "bmiDescription": "مؤشر كتلة الجسم هو قياس يعتمد على الطول والوزن لتقدير دهون الجسم.", - "heightDescription": "يقاس الطول بالسنتيمتر ويستخدم لحساب مؤشر كتلة الجسم وتوصيات الجرعات.", - "weightDescription": "الوزن يساعد في تتبع الصحة العامة والتغذية والتغيرات مع مرور الوقت.", - "bloodPressureDescription": "ضغط الدم يعكس قوة الدم على جدران الشرايين. يظهر كانقباضي/انبساطي.", - "temperatureDescription": "درجة حرارة الجسم تعكس مدى سخونة جسمك وقد تتغير مع العدوى أو الالتهاب.", - "heartRateDescriptionVital": "معدل ضربات القلب يشير إلى عدد نبضات القلب في الدقيقة.", - "respiratoryRateDescription": "معدل التنفس هو عدد الأنفاس المأخوذة في الدقيقة.", - "bmiAdvice": "حافظ على نظام غذائي متوازن ونشاط منتظم. إذا كان مؤشر كتلة جسمك مرتفعًا أو منخفضًا، فكر في استشارة طبيبك.", - "heightAdvice": "لا حاجة لاتخاذ أي إجراء إلا إذا بدا قياسك غير صحيح. قم بتحديثه في زيارتك القادمة.", - "weightAdvice": "راقب تغيرات الوزن. الزيادة أو الخسارة المفاجئة قد تتطلب استشارة طبية.", - "bloodPressureAdvice": "استمر في تتبع ضغط دمك. يجب مناقشة القراءات المرتفعة أو المنخفضة مع طبيبك.", - "temperatureAdvice": "إذا كان لديك حمى مستمرة أو أعراض، اتصل بمقدم الرعاية الصحية.", - "heartRateAdvice": "تتبع اتجاهات معدل ضربات قلبك. إذا شعرت بدوار أو ألم في الصدر، اطلب الرعاية الطبية.", - "respiratoryRateAdvice": "إذا لاحظت ضيقًا في التنفس أو تنفسًا غير طبيعي، اطلب المشورة الطبية.", - "whatShouldIDoNext": "ماذا يجب أن أفعل بعد ذلك؟", - "customizeDrinkCup": "قم بتخصيص كوب مشروبك", - "tipsToStayHydrated": "نصائح للبقاء رطبًا", - "drinkBeforeYouFeelThirsty": "اشرب قبل أن تشعر بالعطش", - "keepRefillableBottleNextToYou": "احتفظ بزجاجة قابلة لإعادة التعبئة بجانبك", - "trackYourDailyIntakeToStayMotivated": "تتبع كمية الماء اليومية للحفاظ على الحافز", - "chooseSparklingWaterInsteadOfSoda": "اختر الماء الفوار بدلاً من الصودا", - "switchCup": "تبديل الكوب", - "plainWater": "ماء عادي", - "yourGoal": "هدفك", - "remaining": "المتبقي", - "hydrationStatus": "حالة الترطيب", - "areYouSureYouWantToCancelAllWaterReminders": "هل أنت متأكد أنك تريد إلغاء جميع تذكيرات الماء؟", - "remindersSet": "تم ضبط التذكيرات!", - "dailyWaterRemindersScheduledAt": "تم جدولة تذكيرات الماء اليومية في:", - "waterConsumption": "استهلاك المياه", - "selectNumberOfReminders": "حدد عدد التذكيرات", - "h2oSettings": "إعدادات H20", - "settingsSavedSuccessfully": "تم حفظ الإعدادات بنجاح", - "yourName": "اسمك", - "ageYears": "العمر (11-120) سنة", - "numberOfRemindersInADay": "عدد التذكيرات في اليوم", - "medications": "الأدوية", - "someRemarksAboutPrescription": "ستجدون هنا بعض الملاحظات حول الوصفة الطبية", - "notifyMeBeforeConsumptionTime": "أبلغني قبل وقت الاستهلاك", - "noMedicationsToday": "لا أدوية اليوم", - "reminders": "تذكيرات", - "reminderAddedToCalendar": "تمت إضافة تذكير إلى التقويم ✅", - "errorWhileSettingCalendar": "حدث خطأ أثناء ضبط التقويم:{error}", - "instructions": "التعليمات", - "requests": "الطلبات", - "thisWeek": "هذا الأسبوع", - "lastMonth": "الشهر الماضي", - "lastSixMonths": "آخر 6 أشهر", - "selectTime": "حدد الوقت", - "pleaseWaitYouWillBeCalledForVitalSigns": "يرجى الانتظار! سيتم استدعاؤك لقياس العلامات الحيوية", - "pleaseVisitRoomForVitalSigns": "يرجى زيارة الغرفة {roomNumber} لقياس العلامات الحيوية", - "pleaseVisitRoomToTheDoctor": "يرجى زيارة الغرفة {roomNumber} لمقابلة الطبيب", - "erOnlineCheckInRequest": "طلب تسجيل الوصول عبر الإنترنت لقسم الطوارئ", - "indoor": "داخلي", - "navigation": "الملاحة", - "health": "الصحة", - "calculators": "الحاسبات", - "converters": "المحولات", - "guide": "الدليل", - "liveCareServiceDesc": "احصل على استشارة طبية من طبيب متخصص عبر مكالمة فيديو", - "bloodGroup": "فصيلة الدم: {bloodType}", - "medicalServices": "الخدمات الطبية", - "insuranceAndPayments": "التأمين والمدفوعات", - "trackerAndOthers": "المتتبع وأخرى", - "bmi": "مؤشر كتلة الجسم", - "yesPleaseINeedHelp": "نعم، من فضلك أريد المساعدة", - "aiOverView": "AI ملخص", - "generateAiAnalysis": "قم بإجراء تحليل لهذا الطلب AI", - "aiAnalysis": "AI تحليل", - "prediction": "تنبؤ", - "prevention": "وقاية", - "disclaimer": "تنصل", - "thisAboveInfo": "المعلومات المذكورة أعلاه مُولّدة بواسطة AI بناءً على نتائج تحاليلك المخبرية، وهي لأغراض إعلامية فقط. لا ينبغي اعتبارها تشخيصًا طبيًا أو خطة علاجية. استشر طبيبك دائمًا للحصول على نصيحة طبية مُخصصة قبل اتخاذ أي قرارات تتعلق بصحتك.", - "aiDisclaimer": "سيتم مشاركة بيانات نتائج تحاليلك المخبرية بشكل آمن مع خدمة الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في تقديم رؤى صحية مُخصصة. هل ترغب في المتابعة؟", - "consent": "موافقة", - "bmiNormalDescription": "مؤشر كتلة جسمك ضمن المعدل الصحي. استمر في اتباع نظام غذائي متوازن، وممارسة النشاط البدني بانتظام، وإجراء الفحوصات الدورية للحفاظ على صحة جيدة. راقب أي تغييرات ملحوظة مع مرور الوقت.", - "bmiUnderweightDescription": "يشير مؤشر كتلة الجسم إلى أنك تعاني من نقص الوزن. لذا، يُنصح بزيادة السعرات الحرارية المتناولة من خلال تناول الأطعمة الغنية بالعناصر الغذائية، وممارسة تمارين المقاومة لبناء العضلات، واستشارة أخصائي رعاية صحية للتحقق من وجود أسباب كامنة.", - "bmiOverweightDescription": "يشير مؤشر كتلة الجسم إلى زيادة الوزن. لذا، يُنصح بالسعي إلى إنقاص الوزن تدريجياً وبشكل مستدام من خلال تقليل السعرات الحرارية بشكل معتدل، وممارسة النشاط البدني بانتظام، واختيار الأطعمة الصحية. استشر طبيبك قبل إجراء أي تغييرات جذرية في نمط حياتك.", - "caloriesMaintenanceDescription": "السعرات الحرارية اليومية المُقدَّرة للحفاظ على وزنك الحالي بناءً على مستوى نشاطك ومعدل الأيض لديك. استخدم هذه الكمية كخط أساس: زدها قليلاً لزيادة الكتلة العضلية، أو قللها قليلاً لإنقاص الوزن تدريجياً. تابع تقدمك وعدّل الخطة كل أسبوعين إلى أربعة أسابيع.", - "caloriesModerateDeficitDescription": "نظام غذائي معتدل لتقليل السعرات الحرارية مصمم لإنقاص الوزن بشكل ثابت ومستدام (عادةً من 0.25 إلى 0.5 كيلوغرام أسبوعيًا). يُنصح بدمجه مع تمارين المقاومة وتناول كمية كافية من البروتين للحفاظ على الكتلة العضلية. تجنب التخفيضات الحادة في السعرات الحرارية واستشر طبيبًا مختصًا إذا كنت تعاني من أي حالة صحية.", - "caloriesAggressiveDeficitDescription": "يؤدي خفض السعرات الحرارية بشكل كبير إلى فقدان الوزن بشكل أسرع، ولكنه قد يصعب الاستمرار عليه ويزيد من خطر نقص العناصر الغذائية والإرهاق وفقدان الكتلة العضلية. لا يُنصح باتباعه على المدى الطويل؛ استشر طبيبًا أو أخصائي تغذية قبل الاستمرار.", - "bodyFatEssentialDescription": "نسبة الدهون في جسمك ضمن النطاق الأساسي/الرياضي. هذا المستوى شائع لدى الرياضيين ويشير إلى انخفاض نسبة الدهون في الجسم. مع أن هذا قد يكون مثاليًا للأداء، تأكد من الحفاظ على تغذية سليمة ووظائف هرمونية متوازنة. استشر طبيبًا إذا شعرت بالتعب، أو واجهت مشاكل هرمونية، أو أي مخاوف صحية أخرى.", - "bodyFatFitnessDescription": "نسبة الدهون في جسمك ضمن النطاق الصحي. وهذا يدل على مستوى صحي من الدهون في الجسم يرتبط بلياقة بدنية جيدة. حافظ على نمط حياتك الحالي، بما في ذلك ممارسة الرياضة بانتظام واتباع نظام غذائي متوازن، للحفاظ على هذا المستوى الصحي.", - "bodyFatAverageDescription": "نسبة الدهون في جسمك ضمن المعدل الطبيعي/المقبول، وهي نسبة تُعتبر صحية لعموم الناس. لتحسين تكوين جسمك، يُنصح بممارسة تمارين القوة، وتمارين القلب والأوعية الدموية، واتباع نظام غذائي متوازن. ركّز على التغييرات التدريجية والمستدامة للحفاظ على صحتك على المدى الطويل.", - "bodyFatObeseDescription": "نسبة الدهون في جسمك أعلى من المعدل الصحي الموصى به. قد يزيد هذا من المخاطر الصحية، بما في ذلك أمراض القلب والأوعية الدموية، والسكري، ومشاكل المفاصل. يُنصح باستشارة طبيب أو أخصائي تغذية معتمد لوضع خطة علاجية شخصية تشمل التغذية المتوازنة، والنشاط البدني المنتظم، وتعديلات نمط الحياة لتحقيق إدارة مستدامة للوزن.", - "bmrNormalDescription": "معدل الأيض الأساسي لديك ضمن النطاق المتوقع لحالتك. معدل الأيض الأساسي هو الطاقة التي يحتاجها جسمك في حالة الراحة للحفاظ على وظائفه الحيوية (التنفس، الدورة الدموية، درجة الحرارة). استخدم هذه القيمة كأساس لتقدير إجمالي احتياجاتك اليومية من الطاقة من خلال تطبيق مُعامل النشاط. حافظ على نظام غذائي متوازن وممارسة النشاط البدني بانتظام لدعم صحة التمثيل الغذائي.", - "bmrLowDescription": "معدل الأيض الأساسي لديك أقل من المعدل الطبيعي لحالتك. تشمل الأسباب المحتملة انخفاض كتلة العضلات، أو تراجع التمثيل الغذائي المرتبط بالتقدم في السن، أو التكيف الأيضي الناتج عن تقييد السعرات الحرارية لفترة طويلة. يُنصح بالتركيز على تمارين المقاومة لبناء العضلات والحفاظ عليها، والتأكد من تناول كميات كافية من البروتين والمغذيات الدقيقة، واستشارة أخصائي رعاية صحية إذا كان هذا الأمر غير متوقع.", - "bmrHighDescription": "معدل الأيض الأساسي لديك أعلى من المتوسط ​​بالنسبة لحالتك. قد يعكس هذا زيادة في كتلة العضلات، أو صغر السن، أو ارتفاع معدل الأيض لديك بشكل طبيعي، مما يعني أنك تحرق طاقة أكبر أثناء الراحة. استخدم هذه المعلومات لتحديد احتياجاتك من السعرات الحرارية: احتياجات أعلى للحفاظ على الوزن أو بناء العضلات، ونقص معتدل في السعرات الحرارية لإنقاص الوزن. استشر طبيبك إذا لاحظت أي تغيرات سريعة غير مبررة.", - "ibwAtTargetDescription": "وزنك في النطاق المثالي. حافظ على عاداتك الحالية — نظام غذائي متوازن ونشاط بدني منتظم — للاستمرار على هذا المستوى.", - "ibwBelowTargetDescription": "أنتِ أقل من الوزن المثالي بمقدار {kg} كجم. يُنصح بزيادة الوزن تدريجيًا من خلال فائضٍ بسيط في السعرات الحرارية، وتناول أطعمة غنية بالعناصر الغذائية، مع ممارسة تمارين المقاومة.", - "ibwAboveTargetDescription": "أنتِ أعلى من الوزن المثالي بمقدار {kg} كجم. يُنصح باتباع خفضٍ تدريجي ومستدام للوزن من خلال عجزٍ معتدل في السعرات الحرارية، وزيادة مستوى النشاط البدني، واتباع تغذية متوازنة.", - "ibwCloseToRangeDescription": "وزنك قريب من النطاق المثالي. حافظ على نظام غذائي متوازن ونشاط بدني منتظم، مع اتباع إرشادات شخصية من مختص صحي عند الحاجة.", - "ibwUnderweightDescription": "أنت أقل من الوزن المثالي بمقدار {kg} كجم. ركّز على زيادة السعرات الحرارية من خلال أطعمة غنية بالعناصر الغذائية، وأعط الأولوية للبروتين وتمارين المقاومة لبناء كتلة صحية، مع اتباع إرشادات شخصية من مختص صحي.", - "ibwUnderweightGenericDescription": "يبدو أنكِ أقل من الوزن المثالي. يُنصح بزيادة السعرات الحرارية من خلال أطعمة غنية بالعناصر الغذائية وممارسة تمارين المقاومة؛ مع الحصول على استشارة مختص صحي عند الحاجة.", - "ibwOverweightDescription": "أنت أعلى من الوزن المثالي بمقدار {kg} كجم. يُنصح باتباع استراتيجية تدريجية ومستدامة لفقدان الوزن — من خلال عجزٍ معتدل في السعرات الحرارية، وممارسة نشاط بدني منتظم، وتغذية متوازنة. يُفضّل استشارة مختص صحي قبل إجراء تغييرات كبيرة.", - "ibwOverweightGenericDescription": "يبدو أنك أعلى من الوزن المثالي. يُنصح باتباع عجزٍ تدريجي ومستدام في السعرات الحرارية مع ممارسة النشاط البدني واتباع تغذية متوازنة؛ مع الحصول على استشارة مختص صحي عند الحاجة.", - "dietVeryLowCarbsDescription": "نهج منخفض الكربوهيدرات جدًا (الكيتوني): تقليل الكربوهيدرات إلى الحد الأدنى مع زيادة الدهون وبروتين معتدل. قد يساعد على فقدان الوزن بسرعة وتحسين التحكم في سكر الدم لدى بعض الأشخاص، لكنه قد يكون صعب الاستمرار عليه وقد يسبب نقصًا في بعض العناصر الغذائية. يجب مراقبة الترطيب ، والحصول على استشارة مختص صحي عند استخدامه لفترة طويلة.", - "dietLowCarbsDescription": "خطة منخفضة الكربوهيدرات وعالية البروتين: تقليل الكربوهيدرات مع زيادة البروتين لدعم الشعور بالشبع والحفاظ على الكتلة العضلية. مفيدة لإدارة الوزن وضبط سكر الدم عند موازنتها مع الخضروات والدهون الصحية. احرص على الحصول على كمية كافية من الألياف والعناصر الدقيقة (الفيتامينات والمعادن).", - "dietModerateCarbsDescription": "تناول معتدل للكربوهيدرات مع توازن في البروتين والدهون: يوفر طاقة مستقرة للأنشطة اليومية والتمارين مع قدر من المرونة. مناسب لكثير من الأشخاص الذين يسعون لإدارة وزن مستدامة وتحسين الأداء.", - "dietUSDAGuidelinesDescription": "نمط متوازن مستند إلى (USDA): يركز على الفواكه والخضروات والحبوب الكاملة والبروتينات قليلة الدهن، مع الحد من السكريات المضافة والدهون المشبعة. يُعد إطارًا قائمًا على الأدلة لدعم الصحة العامة، وكفاية العناصر الغذائية، والوقاية من الأمراض المزمنة.", - "dietZoneDietDescription": "حمية الزون (~40٪ كربوهيدرات / 30٪ بروتين / 30٪ دهون): تركز على التوازن الهرموني وضبط الحصص الغذائية. قد تُحسّن تركيب الجسم ومستوى الطاقة لدى بعض الأشخاص، لكنها تتطلب تخطيطًا جيدًا؛ ويُفضّل تخصيصها بالتعاون مع مختص تغذية.", - "contextDependent": "يعتمد السياق", - "calculatedValue": "القيمة المحسوبة", - "calculationFormula": "صيغة الحساب", - "confirmYourLocation": "قم بتأكيد موقعك", - "confirmYourLocationDesc": "يرجى تأكيد موقعك الحالي لعرض الاتجاهات المناسبة.", - "insideHospital": "أنا داخل المستشفى", - "outsideHospital": "أنا خارج المستشفى", - "servicePriceList": "قائمة أسعار الخدمات", - "servicePriceListDesc": "توضح قائمة أسعار الخدمات أدناه رسوم الخدمات الصحية المقدمة للمرضى بنظام الدفع النقدي. أما فيما يتعلق بالخدمات المشمولة بالتأمين، فسيتم تطبيق التغطية التأمينية والتحقق من الأهلية واحتساب نسب التحمل وفقًا لشروط وثيقة التأمين وجدول المنافع المعتمد لكل شركة تأمين.", - "servicePriceListRights": "يحق للمريض الحصول على متابعة مجانية في غضون 14 يومًا من الزيارة الأولى", - "serviceName": "اسم الخدمة", - "noFreeSlot": "لا توجد مواعيد متاحة في التاريخ المحدد. يرجى اختيار تاريخ آخر أو إعادة المحاولة لاحقاً.", - "noThanksIKnowTheClinic": "لا، شكراً. أنا أعرف العيادة.", - "unableToSendOTP": "تعذر إرسال رمز التحقق لمرة واحدة", - "loadingAIAnalysis": "نقوم بتحليل نتائجك، يرجى التحلي بالصبر والسماح للذكاء الاصطناعي بالقيام بالمهمة، قد يستغرق هذا بعض الوقت.", - "symptoms": "أعراض", - "insuranceInActive": "التأمين غير نشط", - "insuranceInActiveContactSupport": "لديك تأمين ساري المفعول، ولكنه غير مُفعّل حاليًا في مجموعة حبيب الطبية. يُرجى التواصل مع خدمة عملاء مجموعة حبيب الطبية.", - "updateAppNow": "تحديث التطبيق الآن", - "Dr": "الدكتور.", - "reschedulingAppo": "إعادة جدولة الموعد، يرجى الانتظار...", - "invalidEligibility": "لا يمكنك إجراء الدفع عبر الإنترنت لأنك غير مؤهل لاستخدام الخدمة المقدمة.", - "invalidInsurance": "لا يمكنك إجراء الدفع عبر الإنترنت لأنه ليس لديك تأمين صالح.", - "continueCash": "متابعة الدفع نقدًا", - "applewatch": "ساعة آبل", - "applehealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Apple Health على هاتفك", - "unabletodetectapplicationinstalledpleasecomebackonceinstalled": "لا يمكننا اكتشاف التطبيق المثبت على جهازك. يرجى العودة إلى هنا بمجرد تثبيت هذا التطبيق.", - "applewatchshouldbeconnected": "يجب توصيل ساعة آبل", - "samsungwatch": "ساعة سامسونج", - "samsunghealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Samsung Health على هاتفك", - "samsungwatchshouldbeconnected": "يجب توصيل ساعة سامسونج", - "huaweiwatch": "ساعة هواوي", - "huaweihealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Huawei Health على هاتفك", - "huaweiwatchshouldbeconnected": "يجب توصيل ساعة هواوي", - "whoopwatch": "ساعة Whoop", - "whoophealthapplicationshouldbeinstalledinyourphone": "يجب تثبيت تطبيق Whoop Health على هاتفك", - "whoopwatchshouldbeconnected": "يجب توصيل ساعة Whoop", - "updatetheinformation": "سيتيح ذلك جمع أحدث المعلومات من ساعة آبل الخاصة بك", - "timeFor": "الوقت", - "hmgPolicies": "سياسات مجموعة الحبيب الطبية", - "darkMode": "المظهر الداكن", - "featureComingSoonDescription": "هذه الميزة ستتوفر قريباً. نحن نعمل جاهدين لإضافة ميزات أكثر تميزاً إلى التطبيق. انتظرونا لمتابعة التحديثات.", - "generateAiAnalysisResult": "قم بإجراء تحليل لهذا المختبر AI", - "ratings": "التقييمات", - "hmgPharmacyText": "صيدلية الحبيب، المتجر الصيدلاني الإلكتروني المتكامل الذي تقدمه لكم مجموعة خدمات الدكتور سليمان الحبيب الطبية.", - "insuranceRequestSubmittedSuccessfully": "تم إرسال طلب تحديث بيانات التأمين بنجاح. سيتم إعلامك بمجرد الانتهاء.", - "updatingEmailAddress": "جارٍ تحديث عنوان البريد الإلكتروني، يرجى الانتظار...", - "verifyInsurance": "التحقق من التأمين", - "tests": "تحليل", - "calendarPermissionAlert": "يرجى منح إذن الوصول إلى التقويم من إعدادات التطبيق لضبط تذكير تناول الدواء.", - "sortByNearestLocation": "فرز حسب الأقرب إلى موقعك", - "giveLocationPermissionForNearestList": "يرجى منح إذن الوصول إلى الموقع من إعدادات التطبيق لعرض أقرب المواقع.", - "sortByLocation": "الترتيب حسب الموقع", - "timeForFirstReminder": "وقت التذكير الأول", - "reminderRemovalNote": "يمكنك إزالتها من التقويم الخاص بك لاحقاً عن طريق إيقاف تشغيل التذكير", - "communicationLanguage": "لغة التواصل", - "cmcServiceHeader": "فحص صحي شامل: تشخيص متقدم، معلومات صحية مفصلة", - "cmcServiceDescription": "احصل على معلومات تفصيلية عن صحتك من خلال خدمات التشخيص المتقدمة لدينا. افهم جسمك بشكل أفضل لمستقبل صحي.", - "eReferralServiceHeader": "نظام الإحالة الإلكترونية في مستشفى حبيب: تبسيط عملية إحالة المرضى", - "eReferralServiceDescription": "نُسهّل عملية نقل المرضى بسلاسة إلى مستشفى حبيب من خلال نظام الإحالة الإلكترونية الآمن لدينا. نضمن استمرارية الرعاية لكل مريض.", - "bloodDonationServiceHeader": "تبرع بالدم، أنقذ الأرواح. تبرعك يُحدث فرقاً.", - "bloodDonationServiceDescription": "تبرّع بالدم، وأنقذ الأرواح. تبرعك يبعث الأمل. انضم لحملة التبرع بالدم وكن شريان حياة للمحتاجين. كل قطرة تُحدث فرقًا!", - "healthTrackersServiceHeader": "تتبّع مؤشراتك الحيوية بسهولة ويسر", - "healthTrackersServiceDescription": "أدخل بياناتك لمراقبة معدل ضربات القلب وضغط الدم بشكل مستمر، بالإضافة إلى ملخصات دقيقة لأنشطتك اليومية. ابقَ على اطلاع وحسّن صحتك بسهولة.", - "waterConsumptionServiceHeader": "حافظ على رطوبتك، حافظ على صحتك. تتبع كمية الماء التي تشربها يومياً بكل سهولة.", - "waterConsumptionServiceDescription": "أروِ عطشك، وتابع صحتك. راقب كمية الماء التي تتناولها يومياً بكل سهولة باستخدام تطبيقنا سهل الاستخدام، مما يضمن لك الترطيب الأمثل والصحة الجيدة.", - "smartWatchServiceHeader": "قم بمزامنة ساعتك الذكية مع تطبيقات الصحة", - "smartWatchServiceDescription": "قم بتوصيل ساعتك الذكية بسلاسة بتطبيقنا الصحي لتتبع البيانات بسهولة والحصول على رؤى شخصية.", - "liveChatServiceHeader": "مساعدة الخبراء على مدار الساعة طوال أيام الأسبوع\n\nمساعدة", - "liveChatServiceDescription": "هل تحتاج إلى مساعدة؟ تتيح لك خدمة الدردشة المباشرة لدينا التواصل مع فريق دعم الخبراء للإجابة على أي أسئلة لديك حول الميزات أو الإعدادات أو استكشاف الأخطاء وإصلاحها.", - "emergencyServiceHeader": "تسجيل الوصول إلى قسم الطوارئ، أسرع من أي وقت مضى. اتصل بالإسعاف / فريق الاستجابة السريعة على الفور", - "emergencyServiceDescription": "هل تواجه حالة طبية طارئة؟ سيارات الإسعاف وفرق الاستجابة السريعة لدينا جاهزة على مدار الساعة. بالإضافة إلى ذلك، يمكنك تسجيل دخولك إلى قسم الطوارئ بسرعة لتلقي رعاية أسرع.", - "homeHealthCareServiceHeader": "صحتك، في أبهى صورها. رعاية فائقة الجودة، تصلك إلى عتبة دارك.", - "homeHealthCareServiceDescription": "نقدم لكم رعاية صحية عالية الجودة تصلكم إلى عتبة منزلكم. ممرضات ذوات خبرة يقدمون رعاية حانية في راحة منزلكم.", - "profileOnlyText": "الملف الشخصي", - "information": "معلومة", - "noFavouriteDoctors": "ليس لديك أي قائمة مفضلة حتى الآن", - "addDoctors": "إضافة الأطباء", - "favouriteList": "قائمة المفضلة", - "later": "لاحقاً", - "cancelAppointmentConfirmMessage": "هل أنت متأكد من رغبتك في إلغاء هذا الموعد؟", - "acknowledged": "معترف به", - "searchLabResults": "بحث نتائج المختبر", - "callForAssistance": "اتصل للحصول على المساعدة الفورية", - "oneWaySubtitle": "نقل من الموقع إلى المستشفى", - "twoWaySubtitle": "نقل من الموقع إلى المستشفى والعودة مرة أخرى", - "toHospitalSubtitle": "نقل من موقعك الحالي إلى المستشفى", - "fromHospitalSubtitle": "نقل من المستشفى إلى منزلك", - "clinicSchedule": "جدول العيادة", - "doctorAddedToFavourite": "تمت إضافة إلى قائمة المفضلة", - "doctorRemovedFromFavourite": "تمت إزالة من قائمة الأطباء المفضلين", - "addNewFavorite": "إضافة مفضلة جديدة", - "favourites": "المفضلة", - "pendingAncillaryOrders": "مدفوعات طلبات الاضافية", - "liveCarePermissions": "يتطلب لايف كير أذونات الكاميرا والميكروفون وإشعارات، يرجى السماح لها بالمتابعة.", - "containsImage": "يحتوي على صورة", - "servicesSelected": "الخدمة (الخدمات) المختارة", - "selectMedicalFileFromFamily": "اختر ملف طبي من عائلتك", - "anyFileFromHMG": "أي ملف طبي نشط من مجموعة حبيب الطبية", - "noDistance": "المسافة غير متوفرة", - "enterAmount": "يرجى إدخال المبلغ للمتابعة.", - "selectHospitalContinue": "يرجى اختيار المستشفى للمتابعة.", - "year": "سنة", - "notifyPrescription": "أبلغني قبل وقت الاستهلاك", - "doctorRatingAppointment": "طبيب\nتصنيف", - "ratingSubmitted": "تم إرسال التقييم", - "rateTheDoctor": "يرجى تقييم الطبيب", - "downloadInvoice": "تحميل الفاتورة", - "approvalDetails": "تفاصيل الموافقة", - "obese": "سمين", - "overWeight": "وزن زائد", - "underWeight": "وزن ناقص", - "nearestGate": "أقرب بوابة", - "RRTDDetails": "فريق الاستجابة السريعة يقوم بتقديم رعاية صحية متكاملة لجميع الحالات الطارئة و المستقرة، الخدمة تشمل جميع الأعمار بما في ذلك حالات الأطفال.", - "normalMapType": "افتراضي", - "satelliteMapType": "قمر صناعي", - "terrainMapType": "تضاريس", - "hybridMapType": "هجين", - "openCamera": "افتح الكاميرا", - "openGallery": "افتح المعرض", - "openFiles": "فتح الملفات", - "generateAiAnalysisRadResult": "بإنشاء تحليل الذكاء الاصطناعي لهذا التقرير", - "grantLocationPermission": "يرجى منح إذن الوصول إلى الموقع من إعدادات التطبيق للحصول على نتائج أفضل.", - "NoBookedAppointments": "لا توجد مواعيد محجوزة", - "aiDisclaimerRad": "سيتم مشاركة بيانات نتائج الأشعة الخاصة بك بشكل آمن مع محلل الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في توفير رؤى صحية مخصصة. هل ترغب في المتابعة؟", - "imageSizeTooLarge": "حجم الصورة كبير جداً. الحد الأقصى للحجم هو 1 ميجابايت", - "profileImageUpdatedSuccessfully": "تم تحديث صورة الملف الشخصي بنجاح", - "failedToProcessImage": "فشل في معالجة الصورة. يرجى المحاولة مرة أخرى", - "cameraGalleryPermissionMessage": "مطلوب إذن الكاميرا والمعرض لتحميل صورة الملف الشخصي. يرجى منح الأذونات من إعدادات التطبيق.", - "cameraGalleryPermissionRequired": "مطلوب أذونات الكاميرا والمعرض", - "cameraPermissionMessage": "مطلوب إذن الكاميرا لالتقاط الصور. يرجى منح الإذن من إعدادات التطبيق.", - "cameraPermissionRequired": "مطلوب إذن الكاميرا", - "galleryPermissionMessage": "مطلوب إذن المعرض لتحديد الصور. يرجى منح الإذن من إعدادات التطبيق.", - "galleryPermissionRequired": "مطلوب إذن المعرض", - "failedToCheckPermissions": "فشل في التحقق من الأذونات. يرجى المحاولة مرة أخرى", - "takePhotoWithCamera": "التقط صورة بالكاميرا", - "selectFromPhotoLibrary": "اختر من مكتبة الصور", - "selectDocumentFiles": "اختر ملفات المستندات", - "profilePicture": "صورة الملف الشخصي", - "sendingAskDrRequest": "جارٍ إرسال الطلب...", - "askDrRequestSentSuccess": "تم إرسال الطلب بنجاح، وسيتواصل معك الطبيب قريباً.", - "onlineCheckIn24HoursOnly": "لا تتوفر خدمة تسجيل الوصول عبر الإنترنت إلا خلال 4 ساعات من موعد الحجز.لا تتوفر خدمة تسجيل الوصول عبر الإنترنت إلا خلال 4 ساعات من موعد الحجز." -}; -static const Map _en_US = { - "english": "English", - "arabic": "Arabic", - "login": "Login", - "noDataAvailable": "No Data Available", - "noRatingAvailable": "No Rating Available", - "doctorDoesNotHaveRating": "This doctor does not have any ratings yet.", - "confirm": "Confirm", - "loadingText": "Loading, please wait...", - "kilometerUnit": "KM", - "dashboardScreenToolbarTitle": "Home", - "settings": "Settings", - "language": "App Language", - "lanEnglish": "English", - "lanArabic": "العربية", - "cancel": "Cancel", - "done": "Done", - "replay2": "Replay", - "home": "Home", - "services": "SERVICES", - "mySchedule": "My Schedule", - "logout": "Logout", - "respirationRate": "Respiration Rate", - "bookAppo": "Book Appointment", - "searchBy": "Search By:", - "clinic": "Clinic", - "byClinic": "By Clinic", - "hospital": "Hospital", - "byHospital": "By Hospital", - "name": "Name", - "doctor": "Doctor", - "clinicName": "Clinic Name", - "hospitalName": "Hospital Name", - "noClinicFound": "No Clinic Found", - "doctorName": "Doctor Name", - "nearestAppo": "Nearest Appointment", - "searchByDocText": "Type the name of the doctor to help you find him", - "enterDocName": "Enter Doctor name", - "search": "Search", - "noResultFound": "No Result Found", - "noSearchResultFound": "No Result Found", - "pleaseEnterProductName": "Please Enter Product Name", - "bookNow": "Book Now", - "docInfo": "Doctor Information", - "appoInfo": "Appointment Information", - "availableAppo": "Available Appointments", - "gender": "Gender", - "nationality": "Nationality", - "neverWait": "Never wait in queue", - "reminderTitle": "You have appointment with Dr. ", - "appointmentCalendar": "Would you like to set a reminder for this appointment in your calendar?", - "appointmentSuccess": "Appointment is successfully booked", - "docQualifications": "Doctor Qualifications", - "pressAgain": "Press again to exit the app", - "laserMaxLimitReach": "Maximum limit is 90 minutes", - "confirmAppoHeading": "Kindly review your Appointment", - "patientInfo": "Patient Information", - "doctorFilter": "Doctors will be filtered based on your gender and age", - "bookSuccess": "Book Success", - "patientShare": "Patient Share", - "patientShareWithTax": "Patient Share with Tax", - "confirmAppo": "Confirm Appointment", - "confirmLiveCare": "Confirm LiveCare", - "waitingForDoctor": "Waiting for doctor", - "appointment": "Appointment", - "confirmLater": "Confirm Later", - "todoList": "Todo List", - "appoActions": "Appointment Actions", - "date": "Date", - "time": "Time", - "pendingPayment": "Pending for payment", - "payNow": "Pay Now", - "viewQR": "View QR Code", - "instruction": "Instructions", - "livecare": "LiveCare", - "livecareAppo": "LiveCare Appointment", - "agreeTo": "I agree to the", - "termsConditoins": "Terms and Conditions", - "cancelAppoMsg": "Are you sure you want to cancel this appointment?", - "changePayment": "Change Payment Method", - "upcomingNoAction": "No Action Required", - "upcomingConfirm": "Please confirm the appointment to avoid cancellation", - "bookSuccessConfirmMore2412": "The online payment process will be available 24 hours before the appointment.", - "upcomingPaymentPending": "Online Payment will be Activated before 24 Hours of Appointment Time", - "upcomingPaymentNow": "Pay Online now to avoid long waiting queue", - "upcomingQR": "Use the QR Code to Check-In in hospital", - "upcomingVirtual": "This is a virtual appointment, Please adhere to the instructions for a seamless experience.", - "upcomingLivecare": "This is a LiveCare appointment", - "upcomingDetails": "More Details", - "reschedule": "Reschedule", - "raise": "Raise", - "complaint": "Complaint", - "add": "Add", - "reminder": "Reminder", - "location": "Locations", - "online": "Online", - "payment": "Payment", - "onlinePayment": "Online", - "onlinePaymentSubtitle": "Payment", - "cancelNocaps": "Cancel", - "setReminder": "Set Reminder", - "nationalIdNumber": "National ID Number", - "medicalFileNumber": "Medical File Number", - "loginregister": "Login / Register", - "poweredBy": "Powered by", - "welcome": "Welcome", - "welcomeToDrSulaiman": "Welcome to Dr. Sulaiman Al Habib Medical Group", - "patientApp": "Patient App", - "welcomeText": "Dr. Sulaiman Al Habib Mobile Application", - "drSulaimanText": "Dr. Sulaiman Al Habib", - "welcomeText2": "Have you previously visited the hospitals or medical centers of Dr. Sulaiman Al Habib?", - "yes": "Yes", - "no": "No", - "logintyperadio": " Choose from the below options to login to your medical file.", - "registernow": "Register Now", - "nationalID": "Enter the Identification Number", - "nationalId": "National ID", - "fileNo": "File Number", - "fileno": "File No", - "forgotFileNo": "Forgot file Number?", - "forgotFileNoTitle": "Forgot medical file", - "enterNationalId": "Please enter mobile number and identification number", - "profileInfo": "Please enter profile information", - "submit": "Submit", - "forgotDesc": "Enter the mobile number to receive the Medical File Number via SMS", - "dob": "Birth Date:", - "hijriDate": "Hijri Date", - "gregorianDate": "Gregorian Date", - "verifyLoginWith": "Please choose one of the following options to verify", - "registerUser": "Register", - "verifyWithFingerprint": "Biometric", - "verifyWithFaceid": "Face ID", - "verifyWithSms": "SMS", - "verifyWithWhatsapp": "Whatsapp", - "verifyThrough": "Verify Through", - "lastLogin": "LAST LOGIN AT:", - "lastLoginWith": "VERIFICATION TYPE:", - "verifyFingerprint": "To activate the fingerprint login service, please verify data by using one of the following options.", - "searchMedicine": "Search Medicine", - "pharmaciesList": "Pharmacies List", - "searchMedicineHere": "Search Medicine Here", - "description": "Description", - "howToUse": "How to Use", - "price": "Price", - "youCanFindItIn": "You can find it in", - "pleaseEnterMedicineName": "Please Enter Medicine Name", - "verificationMessage": "Please enter the Verification Code sent to", - "validationMessage": "The verification code expires in", - "notification": "Notifications", - "appSettings": "App Settings", - "rateApp": "Rate Our App", - "before": "Before", - "minute": "Minutes", - "hour": "Hour", - "reminderSuccess": "The reminder has been added successfully", - "reminderCancelSuccess": "The reminder has been cancelled successfully", - "patientShareToDo": "Amount before tax: ", - "patientTaxToDo": "Tax amount: ", - "patientShareTotalToDo": "Total amount due: ", - "paymentMethod": "Payment Method", - "noNeedToWaitInLine": "No need to stand in line.", - "useQRAppoAttend": "Use the QR code to register the appointment attendance.", - "passQRAppoAttend": "Pass the QR code through the attendance devices available in the Hospital.", - "sitWaitingQR": "Sit in the waiting rooms until called by the nurse.", - "attendRegisterCode": "Attendance registration code", - "scanQRHospital": "Approach the Online Check-In board in the hospital & scan via NFC or QR Code to Check-In", - "scanNFC": "Scan NFC to Check-In", - "sendEmail": "Send Email", - "success": "Done successfully", - "emailSentSuccessfully": "Email Sent Successfully", - "emailSentError": "Error Sending Email", - "close": "Close", - "closeIt": "Close", - "booked": "Booked", - "confirmed": "Confirmed", - "arrived": "Arrived", - "payNowBookSuccess": "Pay now via Al Habib App", - "payNowBookSuccesstext1": "Pay Now using online payment service from secure payment gateways", - "payNowBookSuccesstext2": "You can also Pay Later via online payment or in Hospital", - "payLater": "Pay Later", - "askDocNotAllowed": "This service will be available for last 15 days’ doctor Visit only", - "moreVerify": "More Verification Options", - "welcomeBack": "Welcome back!", - "accountInfo": "Would you like to login with current username?", - "anotherAcc": "Use Another Account", - "next": "Next", - "firstName": "First Name", - "middleName": "Middle Name", - "lastName": "Last Name", - "female": "Female", - "male": "Male", - "preferredLanguage": "Preferred Language", - "locationsRegister": "Where do you want to create this file?", - "ksa": "KSA", - "dubai": "Dubai", - "enterEmail": "Please Enter Email", - "family": "My Family", - "familyTitle": "My Family Files", - "addNewMember": "Add Family Member", - "sentRequests": "Sent Requests", - "recievedRequests": "Recieved Requests", - "manageFiles": "Manage Family Files", - "oxygenation": "Oxygenation", - "bodyMeasurements": "body Mass Index", - "temperature": "Temperature", - "pulse": "Pulse", - "respiration": "Respiration", - "bloodPressure": "Blood Pressure", - "painScale": "Pain Scale", - "weight": "Weight", - "height": "Height", - "heart": "Heart Rate", - "heightUnit": "height unit", - "weightUnit": "Weight Unit", - "unit": "Unit", - "request": "Request", - "memberName": "Member Name", - "switchLogin": "Switch", - "removeMember": "Remove", - "allowView": "Allow", - "rejectView": "Reject", - "deleteView": "Delete", - "myFamily": "MY FAMILY", - "approvals": "Approvals", - "approvalNo": "Approval No:", - "companyName": "Company Name:", - "receiptOn": "Receipt on:", - "expiryDate": "Expiry Date:", - "expiryPoints": "Expired", - "expiryOn": "Expiry on:", - "procedureName": "Procedure Name:", - "procedure": "Procedure", - "procedureStatus": "Procedure Status: ", - "usageStatus": "Usage Status", - "unusedCount": "Unused Count:", - "totalApproval": "Total approval unused", - "category": "Category: ", - "expirationDate": "Expiration Date: ", - "patientCard": "Patient Card ID: ", - "policyNumber": "Policy Number: ", - "seeDetails": "SEE DETAILS", - "insuranceCards": "Insurance Cards", - "requestType": "Request Type", - "registerInfoFamily": "How would like to add the new member?", - "removeFamilyMember": "Remove this member?", - "myMedicalFile": "My Medical File", - "myMedicalFileSubTitle": "All your medical records", - "viewMore": "View More", - "homeHealthCareService": "Home Health Care Service", - "onlinePharmacy": "Online Pharmacy", - "emergencyService": "Emergency Service", - "onlinePaymentService": "Online Payment Service", - "offersAndPackages": "Online transfer request", - "comprehensiveMedicalCheckup": "Comprehensive Medical Check-up", - "hmgService": "HMG Service", - "viewAllHabibMedicalService": "View All Habib Medical Service", - "viewAll": "View All", - "view": "View", - "contactUs": "Contact Us", - "contactUsLocation": "P.O.Box: 91877 - Riyadh 11643, King Fahad Road - Olaya - Kingdom of Saudi Arabia", - "contactUsTime": "Saturday - Wednesday 8:00 AM - 10 PM, Thursday 8:00 AM- 8:00 PM, Friday 2:00 PM - 8:00 PM", - "viewAllWaysReachUs": "View All Ways Reach Us", - "medicalProfile": "Medical Profile", - "consultation": "Consultation", - "logs": "Logs", - "textToSpeech": "How May I Help You?", - "locationDialogMessage": "Allow the HMG app to access your location will assist you in showing the hospitals according to the nearest to you.", - "userViewRequester": "User Wants to View Your Medical File", - "userView": "User Can View Your Medical File", - "parking": "Parking", - "alhabiServices": "HMG Service", - "parkingTitle": "Car service, car service, service to save parking information, return to it later, 1- By clicking on (Read the code), save the parking data. 2- By clicking on the button (view my car park), it shows you the car’s location in Google Maps. 3- Read another position by pressing the Clear Position Data button.", - "parkingDescription": "Parking service is for you to scan the car location so you can find it easy on you way out. click on (scan parking) button to save the parking location, then (show my park) button will appear to show you the way for the parking. if you want to rescan parking QR just click on (Clear My Data).", - "appointments": "Appointments", - "all2": "All", - "checkinOption": "Check-In", - "readBarcode": "Read Barcode", - "showMyPark": "Show My Park", - "clearMyData": "Clear My Data", - "floor": "Floor:", - "gate": "Gate:", - "building": "Building:", - "branch": "Branch:", - "emergencyServices": "Emergency Services", - "nearester": "Nearest ER", - "locationa": "Location", - "callNow": "Call now", - "ambulancerequest": "Ambulance", - "requestA": "Request", - "noBookedAppointments": "No Booked Appointments", - "noConfirmedAppointments": "No Confirmed Appointments", - "noArrivedAppointments": "No Arrived Appointments", - "myAppointmentsList": "Appointments", - "myAppointments": "My", - "radiology": "Radiology", - "radiologySubtitle": "Result", - "lab": "Lab", - "labSubtitle": "Results", - "medicines": "Medicines", - "medicinesSubtitle": "Prescriptions", - "vitalSigns": "Vital Signs", - "vitalSignsSubTitle": "Reports", - "vitalSignNormal": "Normal", - "vitalSignLow": "Low", - "vitalSignHigh": "High", - "vitalSignObese": "Obese", - "vitalSignOverweight": "Overweight", - "vitalSignUnderweight": "Underweight", - "myMedical": "Active", - "myMedicalSubtitle": "Medications", - "myDoctor": "My Doctors", - "myDoctorSubtitle": "List", - "eye": "Eye", - "eyeSubtitle": "Measurement", - "insurance": "Insurance", - "insuranceSubtitle": "Card", - "updateInsurance": "Update", - "updateInsuranceSubtitle": "Insurance", - "insuranceApproval": "Insurance", - "insuranceApprovalSubtitle": "Approvals", - "allergies": "Allergies", - "allergiesSubtitle": "List", - "myVaccines": "My Vaccines", - "myVaccinesSubtitle": "List", - "medical": "Medical", - "medicalSubtitle": "Report", - "monthly": "Monthly", - "monthlySubtitle": "Report", - "sick": "Sick", - "sickSubtitle": "Leaves", - "myBalance": "My Balance", - "myBalanceSubtitle": "Credit", - "patientCall": "Patient Call", - "patientCallSubtitle": "Service", - "smartWatches": "Smart Watches", - "smartWatchesSubtitle": "Pairing", - "myTrackers": "My Trackers", - "myTrackersSubtitle": "Service", - "askYour": "Ask Your", - "askYourSubtitle": "Doctor", - "internet": "Internet", - "internetSubtitle": "Pairing", - "chatbot": "Chatbot", - "chatbotSubtitle": "", - "timeLine": "Timeline", - "labOrders": "Lab Orders", - "billNo": "Bill No:", - "prescriptions": "Prescriptions", - "history": "History", - "orderNo": "Order No", - "orderDetails": "Order Details", - "deliveryDriverTrack": "Driver Tracking", - "deliveryLocation": "Delivery Location", - "driver": "Driver", - "vitalSign": "Vital Sign", - "monthlyReports": "Monthly Reports", - "km": "KMs:", - "km_": "KM", - "patientHealthSummaryReport": "Patient Health Summary Report", - "toViewTheTermsAndConditions": "To View the Terms and Conditions Report", - "clickHere": "Click here", - "iAgreeToTheTermsAndConditions": "I agree to the terms and conditions ", - "iAgreeToTheTermsAndConditionsSubtitle": "I agree to the terms and conditions ", - "save": "Save", - "userAgreement": "User Agreement", - "updateSuccessfully": "Update Successfully", - "checkVaccineAvailability": "CHECK VACCINE AVAILABILITY", - "myVaccinesAvailability": "My Vaccines Availability", - "paymentService": "Payment Service", - "paymentOnline": "Service", - "onlineCheckIn": "Online Check-In", - "myBalances": "My Balances", - "myWallet": "My Wallet", - "balanceAmount": "Wallet Amount", - "totalBalance": "Total Balance", - "createAdvancedPayment": "Recharge Wallet", - "advancePayment": "Advance Payment", - "advancePaymentLabel": "You can create and add an Advanced Payment for your account or other accounts.", - "fileNumber": "File Number", - "amount": "Amount *", - "depositorEmail": "Depositor Email *", - "notes": "Notes", - "selectPatientName": "Select Patient Name", - "selectFamilyPatientName": "Family Members", - "selectHospital": "Select Hospital", - "selectCity": "Select City", - "city": "City", - "distance": "Distance", - "otherAccount": "Other Account", - "selectBeneficiary": "Select Beneficiary", - "confirmThePayment": "Confirm The Payment", - "depositorName": "Depositor Name", - "mobileNumber": "Mobile Number", - "phoneNumber": "Phone Number", - "country": "Country", - "ok": "Ok", - "averageWaitingTime": "Average Waiting Time:", - "waitingTime": "Expected waiting time:", - "waterConsumedInWeek": "Water consumed in a week", - "waterConsumedInMonth": "Water consumed in a month", - "theVerificationCodeExpiresIn": "The Verification Code Expires in", - "pleaseEnterTheVerificationCode": "Please enter the verification code send to", - "eyeMeasurements": "Eye Measurements", - "measurements": "Measurements", - "classes": "Glasses", - "contactLens": "Contact Lens", - "rightEye": "Right Eye", - "sphere": "Sphere", - "cylinder": "Cylinder", - "axis": "Axis", - "prism": "Prism", - "va": "VA", - "leftEye": "Left Eye", - "brand": "Brand", - "power": "Power", - "diameter": "Diameter", - "activeMedications": "Active Medications", - "expDate": "Active Exp Date :", - "dailyQuantity": "Daily Quantity :", - "addReminder": "Add Reminder", - "cancelReminder": "Cancel Reminder", - "reminderDes": "Please select treatment start day and time to be notified when it's time to take the medicine", - "pleaseSelectAllQuestionToContinue": "Please answer all questions to continue...", - "startDay": "Start Day", - "endDay": "End Day", - "days": "Days ", - "pleaseVerify": "Please Verify ", - "scheduleTime": "Schedule time", - "askDoctor": "Ask Doctor", - "doctorResponses": "Doctor Responses", - "ne_w": "New", - "all": "All", - "dailyWater": "Daily Water Check", - "questionHere": "Enter the question here...", - "viewDoctorResponses": "View Doctor Responses", - "serviceInformationButton": "LOGIN / REGISTER", - "serviceInformationTitle": "Service Information", - "serviceInformation": "Service Information", - "homeHealthCare": "Home Health Care", - "noAppointmentAvailable": "No Available Appointments", - "homeHealthCareText": "This service provides a set of home health care services, continuous and comprehensive follow-up in their places of residence for those who cannot access health facilities, such as (laboratory analyzes - radiology - vaccinations - physical therapy), etc.", - "loginRegister": "Login/Register", - "orderLog": "Order Log", - "infoLab": "This service allows you to view the results of all laboratory tests performed in Al Habib Medical Group as well as sending the report via e-mail.", - "infoRadiology": "This service allows you to view the reports and photos of radiology in Al Habib Medical Group as well as send the report by e-mail.", - "noReviewsAvailable": "No Reviews Available", - "noLocationAvailable": "No Location Available", - "orders": "Orders", - "wishlist": "Wishlist", - "products": "Products", - "reviews": "Reviews", - "brands": "Brands", - "productDetails": "Product Details", - "medicationRefill": "Medication Refill", - "pillReminder": "Pill Reminder", - "shippingAddresses": "Shipping Addresses", - "reachUs": "Reach Us", - "ourLocations": "Our Locations", - "edit": "Edit", - "whatsApp": "Whats App", - "phone": "Phone", - "delete": "Delete", - "deleteAddress": "Are you sure want to delete", - "deletedAddres": "Address has been deleted", - "addAddress": "ADD A NEW ADDRESS", - "addNewAddress": "Add New Address", - "order": "My Order", - "delivered": "Delivered", - "pending": "Pending", - "enterNameHere": "Enter Your Name", - "processing": "Processing", - "cancelled": "Cancelled", - "writeReview": "Write Review", - "shareReview": "SHARE REVIEW", - "review": " reviews", - "viewMedicalFile": "View Details", - "viewAllServices": "View All Services", - "medicalFile": "Medical File", - "verified": "Verified", - "checkup": "Checkup", - "hhcHome": "Home", - "refferal": "E-Refferal", - "refferalTitle": "E-Refferal", - "refferalSubTitle": "Service", - "healthCare": "Health Care", - "emergency": "Emergency", - "erservices": "Emergency", - "services2": "Services", - "cantSeeProfile": "To view your medical profile, please log in or register now", - "loginRegisterNow": "Login or Register Now", - "hmgPharmacy": "HMG Pharmacy", - "ecommerceSolution": "Ecommerce Solution", - "comprehensive": "Comprehensive", - "onlineConsulting": "Online Consulting", - "pendingOrder": " PENDING", - "deliveredOrder": " DELIVERED", - "processingOrder": " PROCESSING", - "cancelledOrder": " CANCELLED", - "compare": " Compare", - "medicationsRefill": " Medication Refill", - "recommended": " Recommended for You", - "myPrescription": " My Prescriptions", - "quantity": " QTY ", - "reviewAppointment": " Review Appointment ", - "backMyAccount": "BACK TO MY ACCOUNT ", - "reviewSuccessful": "Review Successful", - "reviewShared": "Your review has been shared on product review section", - "reviewComment": "Your reviews help other to choose better product", - "shippedMethod": "SHIP BY:", - "orderDetail": "Order Details", - "orderSummary": "Order Summary", - "subtotal": "Subtotal", - "shipping": "Shipping", - "shipBy": "SHIP BY:", - "lakumPoints": "Lakum Points", - "useLakumPoints": "Use Lakum points", - "use": "USE", - "proceedPay": "PROCEED TO PAY", - "vat": "VAT (15%)", - "inclusiveVat": "(inclusive VAT)", - "items": "item(s)", - "checkOut": "CHECK OUT", - "sar": "SAR", - "aed": "AED", - "payOnline": "PAY ONLINE", - "cancelOrder": "CANCEL ORDER", - "confirmAddress": "CONFIRM ADDRESS ", - "confirmLocation": "CONFIRM LOCATION ", - "conditionsHMG": "Terms & Conditions ", - "conditions": "Terms & Conditions of Lakum", - "confirmDeleteMsg": "Are you sure! want to delete ", - "confirmDelete": "DELETE", - "confirmCancellation": "Are you sure! want to cancel this order ", - "orderNumber": "Order#: ", - "orderDate": "Date", - "itemsNo": "items(s)", - "noOrder": "You Don't have any orders.", - "noResult": "No Result.", - "nonRecommended": "No Recommended Products.", - "termsService": "Terms of Service", - "beforeusing": "Before using the checkup, please read Terms of Service.", - "accept": "I read and accept Terms of Service and Privacy Policy", - "dataSafeInfo": "Information that you provide is anonymous and not shared with anyone.", - "dataSafe": " Your data is safe.", - "informational": "Checkup is for informational purposes and is not a qualified medical opinion", - "notUseInEmerbency": "Do not use in emergencies.", - "notUseInEmerbencyDetails": "In case of health emergency, ", - "notUseInEmerbencyDetailsCall": "call the nearest emergency number immediately", - "checkDiagnosis": "Checkup is not a diagnosis.", - "remeberthat": "Remember that", - "loginToUseService": "You need to login to use this service", - "offersAndPromotions": "OFFERS & SPECIAL PROMOTIONS", - "offers": "OFFERS", - "myPrescriptions": "MY PRESCRIPTIONS", - "searchAndScanMedication": "SEARCH & SCAN FOR MEDICATION", - "shopByBrands": "Shop by Brands", - "recentlyViewed": "Recently Viewed", - "bestSellers": "Best Seller", - "deleteAllItems": "Delete All Items", - "total": "Total", - "totalWithColonRight": "Total:", - "selectAddress": "Select Address", - "shippingAddress": "SHIPPING ADDRESS", - "changeAddress": "Change Address", - "selectPaymentOption": "Select Payment Option", - "selectTamaraPlan": "Select Tamara Payment Plan", - "changeMethod": "Change Method", - "reviewOrder": "Review Order", - "active": "Active", - "inactive": "InActive", - "balance": "Balance", - "gained": "GAINED", - "consumed": "Consumed", - "transferred": "Transferred", - "riyal": "RIYAL", - "membersince": "MEMBER SINCE", - "identification": "Identification Number", - "lakumMobile": "Mobile Number", - "waitinggained": "Waiting gained", - "expired": "Expired", - "willBeExpired": "Will Be Expired", - "lakumPoint": "Lakum Points", - "activateLAKUMAccount": "Activate LAKUM Account", - "checkBeneficiary": "CHECK BENEFICIARY", - "beneficiaryName": "Beneficiary Name", - "accountActivation": "Account Activation", - "lakumTransfer": "Lakum Transfer", - "acceptLbl": "Accept", - "declineLbl": "Decline", - "selectGender": "Select Gender", - "iAmA": "I am a ...", - "selectAge": "Select Your Age", - "select": "Select", - "iAm": "I am", - "yearsOld": "years old", - "dragPoint": "Drag point to change your age", - "refine": "Refine", - "subGroup": "Subgroup", - "max": "Max", - "compeleteOrderMsg": "Order has been placed successfully!!", - "addToCompareMsg": "You have added a product to the Compare list", - "itInListMsg": "Item is already in the list", - "compareListFull": "Your compare list is full", - "addQuantity": "You should add quantity", - "addToCartMsg": "You have added a product to the cart", - "addToWishlistMsg": "You have added a product to the Wishlist", - "notifyMeMsg": "You will be notified when product available", - "removeFromWishlistMsg": "You have removed a product from the Wishlist", - "min": "Min", - "reset": "Reset", - "apply": "Apply", - "viewCategorise": "View All Categories", - "viewSubCategorise": "View All Sub Categories", - "categorise": "Categories", - "wishList": "WishList", - "myAccount": "My Account", - "cart": "Cart", - "alhabibapp": "Al Habib app", - "searchProductHere": "Search Product here", - "hhcNotAuthMsg": "This service provides a set of home health care services, continuous and comprehensive follow-up in their places of residence for those who cannot access health facilities, such as (laboratory analyzes - radiology - vaccinations - physical therapy), etc.", - "email": "Email *", - "book": "Book", - "appointmentLabel": "Appointment", - "bloodType": "Blood Type", - "maritalStatus": "Marital status", - "general": "General", - "profile": "My Profile", - "notifications": "Notifications", - "notificationDetails": "Notification Details", - "notificationDetailsa": "Notification Details", - "infoMyDoctorPoints": "", - "infoMyDoctor": "This service allows you to see all the doctors you have visited in Al Habib Medical Group, and through this service:", - "infoPrescriptions": "This service allows you to view all the medical prescriptions issued by Al Habib Medical Group, and through this service, you can:", - "infoMyPrescriptionPoints": "", - "infoInsuranceCards": "This service allows you to view all the insurance cards that recorded during your visits to Al Habib Medical Group in addition to:", - "infoInsuranceCardsPoints": "", - "infoAllergies": "This service allows you to view all types of allergies recorded during your visits to Al Habib Medical Group.", - "infoSickLeaves": "This service allows you to view all sick leaves that were taken in Al Habib Medical Group in addition to:", - "infoSickLeavePoints": "", - "infoApprovals": "This service allows you to view all approvals requests that have been sent to the insurance companies in addition to:", - "infoApprovalPoints": "", - "infoMonthReport": "Upon activation of this service, the system will send a monthly report automatically to the registered email which lists the vital signs and the results for the last visits made in AlHabib Medical Group.", - "languageSetting": "SMS, WhatsApp and Confirmation Calls Language", - "alert": "Alerts", - "emailAlert": "Alert by Email", - "smsAlert": "Alert by SMS", - "contactInfo": "Contact Information", - "emrgName": "Emergency Contact Name", - "emrgNo": "Emergency Contact Number", - "modes": "Modes", - "vibration": "Vibration Touch Feedback", - "blindModes": "Modes for Partially Blind", - "invertTheme": "Invert", - "offTheme": "Off", - "dimTheme": "Dim", - "bwTheme": "Black and White", - "permissions": "Permission", - "cameraPermission": "Camera", - "locationPermission": "Location", - "needPrescription": "This product requires a prescription", - "outOfStockMsg": "You have added product which is out of stock now, Please remove that!", - "noArabicLetters": "It Is Not Allow to Use Arabic Letters", - "noOffersAvailable": "No Offers Available!", - "accessibility": "Accessibility Mode", - "orderStatus": "Order Status", - "findUs": "Find Us", - "liveChat": "Live Chat", - "service": "Service", - "hmgServiceLabel": "HMG Service", - "healthWeatherIndicators": "Health Weather Indicators", - "healthTipsBasedOnCurrentWeather": "Health Tips Based On Current Weather", - "moreDetails": "More details", - "resendOrder": "Refill and Delivery", - "ports": "Ports", - "way": "Way", - "dailyDoses": "Daily Doses", - "period": "Period", - "duration": "المدة", - "cm": "CM", - "ft": "ft", - "kg": "kg", - "lb": "lb", - "birthDate": "Birth Date", - "dateOfBirth": "Date of Birth", - "mass": "Mass", - "tempC": "°C", - "bpm": "bpm", - "respirationSigns": "Respiration", - "sysDias": "SBP/DBP", - "body": "Body Mass", - "bodyString": "Body", - "face": "Face", - "retouch": "Re-touch", - "bikini": "Bikini", - "totalMinutes": "Total Minutes", - "feedback": "Feedback", - "likeToHear": "We would love to hear the feedback, concerns on healthcare services and eServices experience. Please use the below form", - "emptySubject": "Please enter the subject", - "emptyMessage": "Please enter message", - "selectAttachment": "Select Attachment", - "complainAppo": "Complaint for appointment", - "complainAppoAlt": "Complaint without appointment", - "messageType": "Message Type", - "feedbackType": "Feedback Type", - "compliment": "Appreciation", - "suggestion": "Suggestion", - "yourFeedback": "Your feedback was sent", - "selectPart": "Please select the part that complain about", - "number": "Number", - "notClassified": "Not classified", - "selectClinic": "Select Clinic", - "selectProject": "Select Hospital", - "searchItemError": "Item name should be more than 3 character", - "youCanFind": "You Can Find ", - "itemInSearch": " Item in Search", - "bloodDonation": "Blood Donation", - "bloodDonationInfo": "Through this service, you can register your name as a blood donor where the blood bank in the Habib Medical Group will communicate you in case of need for blood type.", - "bloodInstruction": "Enter the required information, in order to register for Blood Donation Service", - "aboutBloodDonation": "About Blood Donation", - "bloodDonationDescription": "Blood donation is a noble act that saves lives. Every single donation can save up to three people. By donating blood, you help patients in need of transfusions, emergencies, and medical treatments.", - "bloodDonationBenefit1": "Safe and quick process (10-15 minutes)", - "bloodDonationBenefit2": "You can donate every 8-12 weeks", - "bloodDonationBenefit3": "Free health check of your blood", - "viewTerms": "To view the terms and conditions", - "wantConnectHmgNetwork": "Dear customer there is no internet access, do you want to connect with HMG network to use our app, make sure you are in range of HMG network", - "failedToAccessHmgServices": "Connected with HMG Network,\n\nBut failed to access HMG services", - "offerAndPackages": "Offers and Packages", - "offerAndPackagesDetails": "This service allows you to view all HMG Offers:", - "invoiceNo": "Invoice No", - "invoiceDate": "Invoice Date", - "specialResult": "Special Result", - "generalResult": "General Result", - "showMoreBtn": "Flow Chart", - "value": "Value", - "report": "Radiology Report", - "openRad": "Open Image", - "sendCopy": "Email the Report", - "appoSurvey": "Survey", - "appoSurveySubtitle": "Survey", - "labResults": "Lab Results", - "doctorRating": "Doctor Rating", - "good": "Good", - "vGood": "Very Good", - "excellent": "Excellent", - "average": "Average", - "infoSigns": "This service allows you to view all vital signs were performed in the Habib Medical Group, e.x (height, weight, body mass index, heart rate, etc.) as well shows some statistics charts.", - "infoAdvancePayment": "This service designed so that you can deposit an amount in advance either in your account or in someone else's account with Al Habib Medical Group.", - "infoMyBalance": "This service allows you to check your balance in all branchs", - "erContant": "This service displays nearest branch among all the branches of Al Habib Medical Group based on your current location.", - "infoAmbulance": "Through this service, you can request evacuation by ambulance, whether from home or to home, in addition to a set of other services", - "infoMyAppointments": "This service allows you to see all the appointment you have visited in Al Habib Medical Group, and through this service:", - "infoTodo": "This service is designed to enable you to have a quick link to the list of tasks that need to be done", - "familyInfo": "Through this service, you will be able to link your family medical files to your medical file so that you can manage their records by login to your medical file.", - "rrtdDetails": "The Rapid response team provides a comprehensive medical service for all sorts of urgent and stable cases of all ages, including adult and paediatric patients, the service is covered by a fully equipped and highly trained medical team capable of providing the best medical care at the patient’s home.", - "onlineCheckInAgreement": "The online check-in is for non-life threatening situation. Call the red crescent (997) or go to the nearest emergency department if there are: \n\nsigns of stroke or heart attack \nhistory of seizure or syncope \nthere is limb or life threatening injury \npicture of severe injuries", - "infoEreferral": "This service allows you to submit a Referral request from any health care providers either inside or outside the kingdom of Saudi Arabia to any of HMG Hospitals, by filling some of the patient's data and attaching the medical reports, moreover you can track the request status (Under process, Accepted or Rejected)", - "erConsultation": "This service allows you to make an online virtual consultation via video call directly with the doctor from anywhere at any time.", - "myInvoice": "List", - "invoiceList": "My Invoice", - "allInvoices": "All Invoices", - "hospitals": "Hospitals", - "clinics": "Clinics", - "doctors": "Doctors", - "selectDoctor": "Select Doctor", - "thisItemIsNotAvailable": "This item is not available", - "beforeAfterImages": "Before & After Images", - "clinicAcceptLivecare": "No need to wait or visit You can now get medical consultation via Video call (LiveCare service) in The name of the clinic clinic and the doctor will contact you immediately", - "livecareModalTop": "This Clinic is Accepting LiveCare services", - "livecarePoint2": "A Specialized doctor will contact you", - "livecarePoint3": "A Doctor will be able to see your full medical file history", - "livecarePoint4": "Free Medicine Delivery Available", - "livecarePoint5": "No need to visit the service is in your place", - "bookImmediateLivecare": "I accept to get the service immediately", - "bookVideoLivecare2": "I don't need to visit the clinic", - "noThankyou": "No Thanks", - "visitClinic": "Schedule appointment with the doctor", - "generateCovidCertificate": "Generate Covid-19 Certificate", - "isReportOutsideKsa": "Is the certificate needed for outside KSA?", - "passportNumber": "Passport Number", - "enterPassportNumber": "Please confirm or update your passport number:", - "validPassportNumber": "Please enter valid passport number", - "continuePlan": "Continue with the treatment plan?", - "aboutApp": "About the app", - "aboutPoints": "Online Appointment Booking & rescheduling, Insurance approval status, Find A doctor, Ask your doctor, Medical prescriptions, Lab results, Hospitals contact numbers, Doctor profiles, Hospitals locations, Pharmacies Locations, Hospital's Virtual Tour, Official Social Media, Vaccines Schedule, Health Calculators, Other Services", - "termsConditions": "These Online Services Terms of Use (Service Terms) govern certain online services provided by Dr Sulaiman Al Habib Medical Services Group Company (HMG, we, us, our)...", - "receiveOtpToast": "Where would you like to receive OTP?", - "enterPhoneNumber": "Enter Phone Number", - "enterEmailDesc": "Enter your email address to complete the process of creating a medical file", - "enterPhoneDesc": "Enter your phone number to receive OTP verification code", - "pleaseChooseOption": "Please select from the below options to receive OTP", - "dontHaveAccount": "Don't have an account?", - "loginOrRegister": "Login or Register", - "myFiles": "My Files", - "resultsPending": "Results Pending", - "resultsAvailable": "Results Available", - "viewReport": "View Report", - "checkAvailability": "Check Availability", - "readInstructions": "Read Instructions", - "searchLabReport": "Search Lab Report", - "prescriptionDeliveryError": "This clinic doesn't support refill", - "prepareToElevate": "Prepared to elevate your health and well-being?", - "iAcceptTermsConditions": "I Accept the Terms and Conditions", - "alreadyHaveAccount": "Already have an account?", - "loginNow": "Login Now", - "notice": "Notice", - "oR": "OR", - "sendOTPWHATSAPP": "Send me OTP on Whatsapp", - "sendOTPSMS": "Send me OTP on SMS", - "fullName": "Full Name", - "married": "Married", - "uae": "United Arab Emirates", - "malE": "Male", - "loginBy": "Login By", - "loginByOTP": "Login By OTP", - "guest": "Guest", - "switchAccount": "Switch Account", - "allSet": "All Set! Now you can login with Face ID or Biometric", - "enableQuickLogin": "Enable Quick Login", - "enableMsg": "Enabling the quick login will verify through your existing device Face ID / Biometric", - "notNow": "Not Now", - "pendingActivation": "Pending Activation", - "awaitingApproval": "Awaiting Approval", - "enterValidNationalId": "Invalid Identification or file number", - "enterValidPhoneNumber": "Please enter a valid phone number", - "cannotEnterSaudiOrUAENumber": "You cannot enter Saudi Arabia (00966) or UAE (00971) phone numbers when 'Others' country is selected", - "ready": "Ready", - "medicalCentersWithCount": "{count} Medical Centers", - "medicalCenters": " Medical Centers", - "hospitalsWithCount": "{count} Hospitals", - "selectRegion": "Select Region", - "selectFacility": "Select Facilities", - "selectFacilitiesSubTitle": "Please select the facility for the appointment", - "selectHospitalSubTitle": "Please select the hospital for the appointment", - "news": "News", - "iAcceptThe": "I Accept the", - "personalDetailsVerification": "Personal Details Verification", - "otpVerification": "OTP Verification", - "weHaveSendOTP": "We have sent you the OTP code on", - "via": "via", - "forRegistrationVerification": "for registration verification", - "didntReceiveIt": "Didn't receive it?", - "resendOTP": "Resend", - "resendIn": "resend in", - "pleaseEnterAnationalID": "Please enter a national ID", - "pleaseEnterAFileNumber": "Please enter a file number", - "pleaseEnterAValidEmail": "Please enter a valid email", - "pleaseEnterFullName": "Please enter full name", - "pleaseAcceptTermsConditions": "Please accept the terms and conditions", - "pleaseEnterAValidIqamaID": "Please enter a valid Iqama ID", - "pleaseEnterAValidNationalID": "Please enter a valid national ID", - "pleaseEnterAValidDateOfBirth": "Please enter a valid date of birth", - "pleaseEnterAValidName": "Please enter a valid name", - "pleaseSelectAGender": "Please select a gender", - "pleaseSelectAMaritalStatus": "Please select a marital status", - "pleaseSelectACountry": "Please select a country", - "pleaseEnterEmail": "Please enter email", - "pleaseEnterAValidEmailFormat": "Please enter a valid email format", - "selectCountry": "Select Country", - "forLoginVerification": "for login verification", - "lastLoginBy": "Last login by", - "searchHospital": "Search Hospital", - "skip": "Skip", - "getStarted": "Get Started", - "onboardingHeading1": "Booking appointment has never been easy", - "onboardingBody1": "In few clicks find yourself having consultation with the doctor of your choice.", - "onboardingHeading2": "Access the medical history on finger tips", - "onboardingBody2": "Keep track on your medical history including labs, prescription, insurance, etc", - "normal": "Normal", - "attention": "Attention", - "monitor": "Monitor", - "noSpecialResult": "No Special Results", - "setTheDateRange": "Set The Date Range", - "historyFlowchart": "History FlowChart", - "to": "to", - "startDate": "Start Date", - "endDate": "End Date", - "hmgHospitals": "HMG Hospitals", - "hmcMedicalClinic": "HMC Medical Centers", - "applyFilter": "Apply Filter", - "facilityAndLocation": "Facility and Location", - "regionAndLocation": "Region And Locations", - "clearAllFilters": "Clear all filters", - "filters": "Filters", - "searchClinic": "Search Clinic", - "walkin": "Walk In", - "continueString": "Continue", - "laserClinic": "Laser Clinic", - "covid_info": "Dr. Sulaiman Al Habib hospitals are conducting a test for the emerging corona virus and issuing travel certificates 24/7 in a short time and with high accuracy. Those wishing to benefit from this service can visit one of Dr. Sulaiman Al Habib branches to conduct a corona test within few minutes, and obtain the result within several hours. Corona Virus Covid 19 testing service with PCR technology to detect the virus according to the highest international standards and with the latest high-precision RT-PCR devices (American GeneXpert and others), That is approved by the Food and Drug Authority as well as by the Saudi Center for Infectious Diseases Prevention.", - "appointmentDetails": "Appointment Details", - "checkingDoctorAvailability": "Checking doctor availability...", - "cancellingAppointmentPleaseWait": "Cancelling Appointment, Please Wait...", - "appointmentCancelledSuccessfully": "Appointment Cancelled Successfully", - "notConfirmed": "Not Confirmed", - "appointmentStatus": "Appointment Status", - "doctorWillCallYou": "The doctor will call you once the appointment time approaches.", - "getDirections": "Get Directions", - "notifyMeBeforeAppointment": "Notify me before the appointment", - "fetchingLabResults": "Fetching Lab Results...", - "fetchingRadiologyResults": "Fetching Radiology Results...", - "fetchingAppointmentPrescriptions": "Fetching Appointment Prescriptions...", - "noPrescriptionsForAppointment": "You don't have any prescriptions for this appointment.", - "amountBeforeTax": "Amount before tax", - "rebookAppointment": "Re-book Appointment", - "fetchingDoctorSchedulePleaseWait": "Fetching Doctor Schedule, Please Wait...", - "pickADate": "Pick a Date", - "confirmingAppointmentPleaseWait": "Confirming Appointment, Please Wait...", - "appointmentConfirmedSuccessfully": "Appointment Confirmed Successfully", - "appointmentPayment": "Appointment Payment", - "checkingPaymentStatusPleaseWait": "Checking payment status, Please wait...", - "paymentFailedPleaseTryAgain": "Payment Failed! Please try again.", - "appointmentCheckIn": "Appointment check in", - "insuranceExpiredOrInactive": "Insurance expired or inactive", - "totalAmountToPay": "Total amount to pay", - "vat15": "VAT 15%", - "liveCare": "LiveCare", - "recentVisits": "Recent Visits", - "favouriteDoctors": "Favourite Doctors", - "searchByClinic": "Search By Clinic", - "tapToSelectClinic": "Tap to select clinic", - "searchByDoctor": "Search By Doctor", - "tapToSelect": "Tap to select", - "searchByRegion": "Search By Region", - "centralRegion": "Central Region", - "immediateConsultation": "Immediate Consultation", - "scheduledConsultation": "Scheduled Consultation", - "pharmaLiveCare": "Pharma LiveCare", - "notSureHelpMeChooseClinic": "Not sure? help me choose a clinic!", - "mentionYourSymptomsAndFindDoctors": "Mention your symptoms and find the list of doctors accordingly", - "immediateService": "Immediate service", - "noNeedToWaitGetMedicalConsultation": "No need to wait, you will get medical consultation immediately via video call", - "noVisitRequired": "No visit required", - "doctorWillContact": "Doctor will contact", - "specialisedDoctorWillContactYou": "A specialised doctor will contact you and will be able to view your medical history", - "freeMedicineDelivery": "Free medicine delivery", - "offersFreeMedicineDelivery": "Offers free medicine delivery for the LiveCare appointment", - "dentalChiefComplaints": "Dental Chief Complaints", - "viewAvailableAppointments": "View available appointments", - "doctorProfile": "Doctor Profile", - "waitingAppointment": "Waiting Appointment", - "hospitalInformation": "Hospital Information", - "fetchingAppointmentShare": "Fetching Appointment Share...", - "bookingYourAppointment": "Booking your appointment...", - "selectLiveCareClinic": "LiveCare Clinic", - "checkingForExistingDentalPlan": "Checking for an existing dental plan, Please wait...", - "dentalTreatmentPlan": "Dental treatment plan", - "youHaveExistingTreatmentPlan": "You have an existing treatment plan: ", - "mins": "Mins", - "totalTimeRequired": "Total time required", - "wouldYouLikeToContinue": "Would you like to continue it?", - "chooseDoctor": "Choose Doctor", - "viewNearestAppos": "View nearest available appointments", - "noDoctorFound": "No Doctor found for selected criteria...", - "yesPleasImInAHurry": "Yes please, I am in a hurry", - "fetchingFeesPleaseWait": "Fetching fees, Please wait...", - "noThanksPhysicalVisit": "No, Thanks. I would like a physical visit", - "offline": "Offline", - "videoCall": "Video Call", - "liveVideoCallWithHMGDoctors": "Live Video Call with HMG Doctors", - "liveAudioCallWithHMGDoctors": "Live Audio Call with HMG Doctors", - "audioCall": "Audio Call", - "phoneCall": "Phone Call", - "livePhoneCallWithHMGDoctors": "Live Phone Call with HMG Doctors", - "reviewLiveCareRequest": "Review LiveCare Request", - "selectedLiveCareType": "Selected LiveCare Type", - "selectLiveCareCallType": "Select LiveCare call type", - "confirmingLiveCareRequest": "Confirming LiveCare request, Please wait...", - "unknownErrorOccurred": "Unknown error occurred...", - "liveCarePermissionsMessage": "LiveCare requires Camera, Microphone, Location & Notifications permissions to enable virtual consultation between patient & doctor, Please allow these to proceed.", - "liveCarePayment": "LiveCare Payment", - "mada": "Mada", - "visaOrMastercard": "Visa or Mastercard", - "tamara": "Tamara", - "fetchingApplePayDetails": "Fetching Apple Pay details, Please wait...", - "liveCarePendingRequest": "LiveCare Pending Request", - "callLiveCareSupport": "Call LiveCare Support", - "whatIsWaitingAppointment": "What is Waiting Appointment?", - "waitingAppointmentsFeature": "The waiting appointments feature allows you to book an appointment while you are inside the hospital building, and in case there is no available slot in the doctor's schedule.", - "appointmentWithDoctorConfirmed": "The appointment with the doctor is confirmed, but the time of entry is uncertain.", - "paymentWithinTenMinutes": "Note: You must have to pay within 10 minutes of booking, otherwise your appointment will be cancelled automatically", - "liveLocation": "Live Location", - "verifyYourLocationAtHospital": "Verify your location to be at hospital to check in", - "error": "Error", - "ensureWithinHospitalLocation": "Please ensure you're within the hospital location to perform online check-in.", - "nfcNearFieldCommunication": "NFC (Near Field Communication)", - "scanPhoneViaNFC": "Scan your phone via NFC board to check in", - "qrCode": "QR Code", - "scanQRCodeToCheckIn": "Scan QR code with your camera to check in", - "processingCheckIn": "Processing Check-In...", - "bookingWaitingAppointment": "Booking Waiting Appointment, Please wait...", - "enterValidIDorIqama": "Please enter a valid national ID or file number", - "selectAppointment": "Select Appointment", - "rebookSameDoctor": "Rebook with same doctor", - "queueing": "Queueing", - "inQueue": "In Queue", - "yourTurn": "Your Turn", - "halaFirstName": "Hala {firstName}!!!", - "thankYouForPatience": "Thank you for your patience, here is your queue number.", - "servingNow": "Serving Now", - "callForVitalSigns": "Call for vital signs", - "callForDoctor": "Call for Doctor", - "thingsToAskDoctor": "Things to ask your doctor today", - "improveOverallHealth": "What can I do to improve my overall health?", - "routineScreenings": "Are there any routine screenings I should get?", - "whatIsThisMedicationFor": "What is this medication for?", - "sideEffectsToKnow": "Are there any side effects I should know about?", - "whenFollowUp": "When should I come back for a follow-up?", - "goToHomepage": "Go to homepage", - "appointmentsList": "Appointments List", - "allAppt": "All Appt.", - "upcoming": "Upcoming", - "completed": "Completed", - "noAppointmentsYet": "You don't have any appointments yet.", - "viewProfile": "View Profile", - "choosePreferredHospitalForService": "Choose your preferred hospital for the service", - "noHospitalsFound": "No hospitals Found", - "cancelOrderConfirmation": "Are you sure you want to cancel this order?", - "orderCancelledSuccessfully": "Order has been cancelled successfully", - "requestID": "Request ID:", - "noCMCOrdersYet": "You don't have any CMC orders yet.", - "cmcOrders": "CMC Orders", - "summary": "Summary", - "selectedService": "Selected Service", - "requestSubmittedSuccessfully": "Your request has been successfully submitted.", - "hereIsYourRequestNumber": "Here is your request #: ", - "pleaseSelectHospitalToContinue": "Please select a hospital to continue", - "confirmSubmitRequest": "Are you sure you want to submit this request?", - "pendingOrderWait": "You have a pending order. Please wait for it to be processed.", - "noServicesAvailable": "No services available", - "selectAService": "Select a Service", - "comprehensiveCheckup": "Comprehensive Checkup", - "viewNearestHMGLocations": "View your nearest HMG locations", - "provideFeedbackOnServices": "Provide your feedback on our services", - "liveChatWithHMG": "Live chat option with HMG", - "send": "Send", - "status": "Status", - "sendingFeedback": "Sending Feedback...", - "selectFeedbackType": "Select Feedback Type", - "loadingAppointmentsList": "Loading appointments list...", - "noAppointmentsForFeedback": "You do not have any appointments to submit a feedback.", - "selectedAppointment": "Selected Appointment:", - "subject": "Subject", - "enterSubjectHere": "Enter subject here", - "message": "Message", - "enterMessageHere": "Enter message here", - "filesSelected": "{count} file(s) selected", - "otherDetails": "Other Details", - "medicalReport": "Medical Report", - "medicalReportNumber": "Medical Report {number}", - "patientIsInsured": "Patient is Insured", - "insuranceDocument": "Insurance Document", - "selectBranch": "Select Branch", - "patientInformation": "Patient information", - "patientLocation": "Where the patient located", - "identificationNumber": "Identification Number", - "enterIdentificationNumber": "Enter Identification Number*", - "patientName": "Patient Name*", - "referralRequesterInformation": "Referral requester information", - "enterReferralRequesterName": "Enter Referral Requester Name*", - "requesterName": "Requester Name", - "relationship": "Relationship", - "selectRelation": "Select Relation", - "otherName": "Other Name", - "otherNameHint": "Other Name*", - "requesterNameRequired": "Referral requester name is required", - "selectRelationshipRequired": "Please select a relationship", - "otherRelationshipNameRequired": "Other relationship name is required", - "identificationNumberRequired": "Identification number is required", - "patientNameRequired": "Patient name is required", - "enterPatientPhoneRequired": "Please Enter patient phone number", - "selectPatientCityRequired": "Please select patient city", - "medicalReportRequired": "At least one medical report is required", - "selectBranchRequired": "Please select a branch", - "insuranceDocumentRequired": "Insurance document is required for insured patients", - "searchResult": "Search Result", - "referralNo": "Referral No {number}", - "eReferral": "E Referral", - "referralCreatedSuccessfully": "Your Referral has been created Successfully.", - "hereIsYourReferralNumber": "Here is your Referral #: ", - "searchEReferral": "Search E-Referral", - "enterRequiredInfoToSearch": "Please enter the required information to search for an e-referral", - "selectPickupDirection": "Select Pickup Direction", - "selectDirection": "Select Direction", - "toHospital": "To Hospital", - "fromHospital": "From Hospital", - "selectWay": "Select Way", - "oneWay": "One Way", - "twoWay": "Two Way", - "selectPickupDetails": "Select Pickup Details", - "pleaseSelectDetailsOfPickup": " Please select the details of pickup", - "selectDetails": "Select Details", - "work": "Work", - "pick": "Pick", - "insideTheHome": "Inside the home", - "haveAnyAppointment": "Have any appointment", - "amountPaidAtHospital": "Amount will be paid at the hospital", - "submitRequest": "Submit Request", - "enterPickupLocationManually": "Enter Pickup Location Manually", - "enterPickupLocation": "Enter Pickup Location", - "trackingDetails": "Tracking Details", - "cancelRequest": "Cancel Request", - "shareLocationWhatsapp": "Share Your Live Location on Whatsapp", - "pleaseWaitForCall": "Please wait for the call", - "toHospitalLower": "to hospital", - "contact": "Contact", - "failed": "Failed", - "confirmationCall": "Confirmation Call", - "pickupFromHome": "Pickup Up from Home", - "onTheWayToHospital": " On The Way To Hospital", - "arrivedAtHospital": "Arrived at Hospital", - "orderCancel": "Order Cancel", - "emergencyCheckIn": "Emergency Check-In", - "erOnlineCheckInDescription": "This service lets patients register their ER appointment prior to arrival.", - "erOnlineCheckInSuccess": "Your ER Online Check-In has been successfully done. Please proceed to the waiting area.", - "erOnlineCheckInError": "Unexpected error occurred during check-in. Please contact support.", - "fetchingHospitalsList": "Fetching hospitals list...", - "fetchingPaymentInformation": "Fetching payment information...", - "erVisitDetails": "ER Visit Details", - "erClinic": "ER Clinic", - "vatWithAmount": "VAT 15% ({amount})", - "erAppointmentBookedSuccess": "Your appointment has been booked successfully. Please perform Check-In once you arrive at the hospital.", - "underProcessing": "Under processing", - "canceledByPatient": "Cancelled by patient", - "rapidResponseTeam": "Rapid Response Team", - "allFacilities": "All Facilities", - "selectLocation": "Select Location", - "pleaseSelectTheLocation": "Please select the location", - "viewLocationGoogleMaps": "View Location on Google Maps", - "callAmbulance": "Call Ambulance", - "requestAmbulanceInEmergency": "Request an ambulance in emergency from home or hospital", - "confirmation": "Confirmation", - "areYouSureYouWantToCallAmbulance": "Are you sure you want to call an ambulance?", - "getDetailsOfNearestBranch": "Get the details of nearest branch including directions", - "areYouSureYouWantToCallRRT": "Are you sure you want to call Rapid Response Team (RRT)?", - "priorERCheckInToSkipLine": "Prior ER Check-In to skip the line & payment at the reception.", - "areYouSureYouWantToMakeERCheckIn": "Are you sure you want to make ER Check-In?", - "checkingYourERAppointmentStatus": "Checking your ER Appointment status...", - "transportOptions": "Transport Options", - "selectHospitalForAdvancePayment": "Please select the hospital you want to make an advance payment for.", - "recharge": "Recharge", - "activityLevel": "Activity Level", - "selectActivityLevel": "Select Activity Level", - "caloriesPerDay": "Calories Per Day", - "dietType": "Diet Type", - "selectDietType": "Select Diet Type", - "bodyFrameSize": "Body Frame Size", - "selectBodyFrameSize": "Select Body Frame Size", - "averageCycleLength": "Average Cycle Length (Usually 28 days)", - "averageLutealPhase": "Average Luteal Phase Length(Usually 14 days)", - "convert": "Convert", - "calculate": "Calculate", - "healthCalculators": "Health Calculators", - "healthConverters": "Health Converters", - "generalHealth": "General Health", - "relatedToBMICalories": "Related To BMI, calories, body fat, etc to stay updated with your health.", - "selectCalculator": "Select Calculator", - "womensHealth": "Women's Health", - "relatedToPeriodsOvulation": "Related To periods, ovulation, pregnancy, and other topics.", - "bloodSugar": "Blood Sugar", - "trackYourGlucoseLevels": "Track your glucose levels, understand trends, and get personalized insights for better health.", - "bloodCholesterol": "Blood Cholesterol", - "monitorCholesterolLevels": "Monitor cholesterol levels, assess heart health risks, and take proactive steps for well-being.", - "triglyceridesFatBlood": "Triglycerides Fat Blood", - "understandTriglyceridesImpact": "Understand triglycerides' impact on heart health with personalized insights and expert recommendations.", - "bmiCalculator": "BMI\nCalculator", - "caloriesCalculator": "Calories\nCalculator", - "bmrCalculator": "BMR\nCalculator", - "idealBodyWeight": "Ideal Body\nWeight", - "bodyFatCalculator": "Body Fat\nCalculator", - "carbsProteinFat": "Carbs\nProtein & Fat", - "ovulationPeriod": "Ovulation\nPeriod", - "deliveryDueDate": "Delivery\nDue Date", - "low": "Low", - "preDiabetic": "Pre-diabetic", - "high": "High", - "elevated": "Elevated", - "recorded": "Recorded", - "noRecordsYet": "No records yet", - "lastRecord": "Last Record", - "addBloodSugar": "Add Blood Sugar", - "addBloodPressure": "Add Blood Pressure", - "addWeight": "Add Weight", - "bloodSugarDataSavedSuccessfully": "Blood Sugar Data saved successfully", - "bloodPressureDataSavedSuccessfully": "Blood Pressure Data saved successfully", - "weightDataSavedSuccessfully": "Weight Data saved successfully", - "pleaseWait": "Please wait", - "selectUnit": "Select Unit", - "selectMeasureTime": "Select Measure Time", - "selectArm": "Select Arm", - "enterBloodSugar": "Enter Blood Sugar", - "enterSystolicValue": "Enter Systolic Value", - "enterDiastolicValue": "Enter Diastolic Value", - "enterWeight": "Enter Weight", - "selectDuration": "Select Duration", - "systolic": "Systolic", - "diastolic": "Diastolic", - "sendReportByEmail": "Send Report by Email", - "enterYourEmailToReceiveReport": "Enter your email address to receive the report", - "addNewRecord": "Add new Record", - "healthTrackers": "Health Trackers", - "monitorBloodPressureLevels": "Monitor your blood pressure levels, track systolic and diastolic readings, and maintain a healthy heart.", - "trackWeightProgress": "Track your weight progress, set goals, and maintain a healthy body mass for overall wellness.", - "bookAppointment": "Book Appointment", - "completeCheckup": "Complete Checkup", - "indoorNavigation": "Indoor Navigation", - "eReferralServices": "E-Referral Services", - "dailyWaterMonitor": "Daily Water Monitor", - "fetchingYourWaterIntakeDetails": "Fetching your water intake details.", - "healthCalculatorsServices": "Health\nCalculators", - "healthConvertersServices": "Health\nConverters", - "smartWatchesServices": "Smart\nWatches", - "exploreServices": "Explore Services", - "medicalAndCareServices": "Medical & Care Services", - "hmgServices": "HMG Services", - "personalServices": "Personal Services", - "habibWallet": "Habib Wallet", - "loginToViewWalletBalance": "Login to view your wallet balance", - "loginToViewMedicalFile": "Login to view your medical file", - "addMember": "Add Member", - "addFamilyMember": "Add Family Member", - "pleaseFillBelowFieldToAddNewFamilyMember": "Please fill the below field to add a new family member to your profile", - "healthTools": "Health Tools", - "supportServices": "Support Services", - "virtualTour": "Virtual Tour", - "carParking": "Car Parking", - "latestNews": "Latest News", - "hmgContact": "HMG Contact", - "durationCannotExceed90": "Duration can not exceed 90 mins", - "unexpectedError": "Unexpected Error Occurred", - "gettingAmbulanceTransportOption": "Getting Ambulance Transport Option", - "fetchingAppointment": "Fetching Appointment", - "doYouWantToCancelTheRequest": "Do you want to cancel the request", - "cancellingRequest": "Cancelling request", - "fetchingTermsAndConditions": "Fetching Terms And Conditions", - "selectLocationPrescriptionDelivery": "Please select the location for prescription delivery", - "noRadiologyOrders": "No Radiology Orders Found", - "ageIsRequired": "Age is required", - "invalidAge": "Invalid age", - "ageMustBeBetween11And120": "Age must be between 11 and 120", - "heightIsRequired": "Height is required", - "invalidHeight": "Invalid height", - "weightIsRequired": "Weight is required", - "invalidWeight": "Invalid weight", - "timeToDrinkWater": "Time to Drink Water! 💧", - "stayHydratedDrinkWater": "Stay hydrated! Drink {amount}ml of water.", - "visitPharmacyOnline": "Visit Pharmacy Online", - "howAreYouFeelingToday": "How are you feeling today?", - "checkYourSymptomsWithScale": "Check your symptoms with this scale", - "checkYourSymptoms": "Check your symptoms", - "noUpcomingAppointmentPleaseBook": "You do not have any upcoming appointment. Please book an appointment", - "youHaveEROnlineCheckInRequest": "You have ER Online Check-In Request", - "quickLinks": "Quick Links", - "viewMedicalFileLandingPage": "View medical file", - "immediateLiveCareRequest": "Immediate LiveCare Request", - "yourTurnIsAfterPatients": "Your turn is after {count} patients. Please wait for your turn, The doctor will call you shortly.", - "dontHaveHHCOrders": "You don't have any Home Health Care orders yet.", - "hhcOrders": "HHC Orders", - "requestedServices": "Requested Services", - "selectServices": "Select Services", - "selectedServices": "Selected Services", - "createNewRequest": "Create new request", - "youHaveNoPendingRequests": "You have no pending requests.", - "noInsuranceDataFound": "No insurance data found...", - "noInsuranceUpdateRequest": "No insurance update requests found.", - "policyNumberInsurancePage": "Policy: {number}", - "insuranceExpired": "Insurance Expired", - "insuranceActive": "Insurance Active", - "patientCardID": "Patient Card ID: {id}", - "noInsuranceApprovals": "You don't have any insurance approvals yet.", - "noInsuranceWithHMG": "You don't have insurance registered with HMG.", - "referenceRange": "Reference Range", - "downloadReport": "Download report", - "generatingReport": "Generating report, Please wait...", - "noLabResults": "You don't have any lab results yet.", - "labResultDetails": "Lab Result Details", - "resultOf": "Result of", - "whatIsThisResult": "What is this result?", - "lastTested": "Last Tested", - "byVisit": "By Visit", - "byTest": "By Test", - "results": "results", - "viewResults": "View Results", - "rebook": "Rebook", - "noOphthalmologyAppointments": "No Ophthalmology appointments found...", - "noVitalSignsRecordedYet": "No vital signs recorded yet", - "appointmentsAndVisits": "Appointments & visits", - "labAndRadiology": "Lab & Radiology", - "activeMedicationsAndPrescriptions": "Recent Prescriptions", - "allPrescriptions": "All Prescriptions", - "allMedications": "All Medications", - "youDontHaveAnyPrescriptionsYet": "You don't have any prescriptions yet.", - "youDontHaveAnyCompletedVisitsYet": "You don't have any completed visits yet", - "others": "Others", - "allergyInfo": "Allergy Info", - "vaccineInfo": "Vaccine Info", - "updateInsuranceInfo": "Update Insurance", - "myInvoicesList": "My Invoices List", - "ancillaryOrdersList": "Ancillary Orders List", - "youDontHaveAnySickLeavesYet": "You don't have any sick leaves yet.", - "medicalReports": "Medical Reports", - "sickLeaveReport": "Sick Leave Report", - "weightTracker": "Weight Tracker", - "askYourDoctor": "Ask Your Doctor", - "internetPairing": "Internet Pairing", - "requested": "Requested", - "youDontHaveAnyMedicalReportsYet": "You don't have any medical reports yet.", - "requestMedicalReport": "Request medical report", - "youDoNotHaveAnyAppointmentsToRequestMedicalReport": "You do not have any appointments to request a medical report.", - "areYouSureYouWantToRequestMedicalReport": "Are you sure you want to request a medical report for this appointment?", - "yourMedicalReportRequestSubmittedSuccessfully": "Your medical report request has been successfully submitted.", - "monthlyHealthSummaryReportDisclaimer": "This monthly health summary report reflects the health indicators and analysis results of the latest visits. Please note that this will be sent automatically from the system and it's not considered as a official report so no medical decision should be taken based on it", - "updatingMonthlyReportStatus": "Updating Monthly Report Status...", - "monthlyReportStatusUpdatedSuccessfully": "Monthly Report Status Updated Successfully", - "whoCanViewMyMedicalFile": "Who can view my medical file?", - "acceptedYourRequestToBeYourFamilyMember": "{status} your request to be your family member", - "canViewYourFile": "can view your file", - "hasARequestPendingToBeYourFamilyMember": "has a request {status} to be your family member", - "wantsToAddYouAsTheirFamilyMember": "wants to add you as their family member", - "rejectedYourRequestToBeYourFamilyMember": "{status} your request to be your family member", - "rejectedYourFamilyMemberRequest": "{status} your family member request", - "notAvailable": "N/A", - "selectAProfile": "Please select a profile", - "switchFamilyFile": "Switch from the below list of medical files", - "medicalFiles": "Medical Files", - "addANewFamilyMember": "Add a new family member", - "viewInvoiceDetails": "View invoice details", - "outPatient": "OutPatient", - "invoiceDetails": "Invoice Details", - "sendingEmailPleaseWait": "Sending email, Please wait...", - "emailSentSuccessfullyMessage": "Email sent successfully.", - "discount": "Discount", - "paid": "Paid", - "fetchingInvoiceDetails": "Fetching invoice details, Please wait...", - "scanQRCode": "Scan QR code", - "parkingSlotDetails": "Parking Slot Details", - "slotNumber": "Slot: {code}", - "basement": "Basement: {description}", - "parkingDate": "Date: {date}", - "parkedSince": "Parked Since: {time}", - "resetDirection": "Reset Direction", - "noPrescriptionOrdersYet": "You don't have any prescription orders yet.", - "fetchingPrescriptionPDFPleaseWait": "Fetching prescription PDF, Please wait...", - "ratingValue": "Rating: {rating}", - "downloadPrescription": "Download Prescription", - "fetchingPrescriptionDetails": "Fetching prescription details...", - "switchBackFamilyFile": "Switch Back To Family File", - "profileAndSettings": "Profile & Settings", - "quickActions": "Quick Actions", - "notificationsSettings": "Notifications Settings", - "touchIDFaceIDServices": "Touch ID / Face ID Services", - "personalInformation": "Personal Information", - "updateEmailAddress": "Update Email Address", - "helpAndSupport": "Help & Support", - "permissionsProfile": "Permissions", - "privacyPolicy": "Privacy Policy", - "deactivateAccount": "Deactivate account", - "ageYearsOld": "{age} {yearsOld}", - "youDontHaveRadiologyOrders": "You don't have any radiology results yet.", - "radiologyResult": "Radiology Result", - "viewRadiologyImage": "View Radiology Image", - "rateClinic": "Rate Clinic", - "back": "Back", - "rateDoctor": "Rate Doctor", - "howWasYourLastVisitWithDoctor": "How was your last visit with doctor?", - "dateOfBirthSymptoms": "What is your Date of Birth?", - "genderSymptoms": "What is your gender?", - "heightSymptoms": "How tall are you?", - "weightSymptoms": "What is your weight?", - "femaleGender": "Female", - "previous": "Previous", - "selectedOrgans": "Selected Organs", - "noOrgansSelected": "No organs selected yet", - "organSelector": "Organ Selector", - "noPredictionsAvailable": "No Predictions available", - "areYouSureYouWantToRestartOrganSelection": "Are you sure you want to restart the organ selection?", - "possibleConditions": "Possible Conditions", - "pleaseSelectAtLeastOneRiskBeforeProceeding": "Please select at least one risk before proceeding", - "aboveYouSeeCommonRiskFactors": "Above you see the most common risk factors. Although /diagnosis may return questions about risk factors, ", - "readMore": "Read more", - "riskFactors": "Risk Factors", - "noRiskFactorsFound": "No risk factors found", - "basedOnYourSelectedSymptomsNoRiskFactors": "Based on your selected symptoms, no additional risk factors were identified.", - "messageNotification": "Message", - "attachedImage": "Attached Image", - "failedToLoadImage": "Failed to load image", - "typeNotification": "Type", - "pleaseSelectAtLeastOneOptionBeforeProceeding": "Please select at least one option before proceeding", - "suggestions": "Suggestions", - "pleaseGoBackAndSelectOrgansFirst": "Please go back and select organs first", - "symptomsSelector": "Symptoms Selector", - "possibleSymptomsRelatedTo": "Possible symptoms related to", - "suggestionAIListDescription": "This is a list of symptoms suggested by our AI, based on the information gathered so far during the interview", - "analyzing": "Analyzing...", - "seekMedicalAdvice": "Seek Medical Advice", - "monitorDescription": "No need to seek medical advice. Just keep healthy routine.", - "seekMedicalAdviceDescription": "Not emergency but better to monitor the symptoms.", - "emergencyDescription": "Need to consult doctor as soon as possible before getting too late.", - "colorScience": "Color Science", - "noNeedToVisitDoctor": "No need to visit doctor", - "commonSymptom": "Common Symptom", - "notApplicable": "Not Applicable", - "emergencyTriage": "Emergency", - "emergencyEvidenceDetected": "Emergency evidence detected. Please seek medical attention.", - "noQuestionItemsAvailable": "No question items available", - "pleaseAnswerAllQuestionsBeforeProceeding": "Please answer all questions before proceeding", - "triage": "Triage", - "areYouSureYouWantToExitProgress": "Are you sure you want to exit? Your progress will be lost.", - "noQuestionAvailable": "No question available", - "possibleSymptom": "Possible symptom: ", - "symptomsCheckerFindingScore": "- Symptoms checker finding score", - "notSet": "Not set", - "years": "Years", - "symptomsChecker": "Symptoms Checker", - "helloIsYourInformationUpToDate": "Hello {name}, Is your information up to date?", - "noEditAll": "No, Edit all", - "yesItIs": "Yes, It is", - "age": "Age", - "youDontHaveAnyAncillaryOrdersYet": "You don't have any ancillary orders yet.", - "invoiceWithNumber": "Invoice: {invoiceNo}", - "queued": "Queued", - "checkInReady": "Check-in Ready", - "checkIn": "Check In", - "viewDetails": "View Details", - "selectPaymentMethod": "Select Payment Method", - "processingPaymentPleaseWait": "Processing payment, Please wait...", - "finalizingPaymentPleaseWait": "Finalizing payment, Please wait...", - "generatingInvoicePleaseWait": "Generating invoice, Please wait...", - "hereIsYourInvoiceNumber": "Here is your invoice #: ", - "paymentCompletedSuccessfully": "Payment Completed Successfully", - "failedToInitializeApplePay": "Failed to initialize Apple Pay. Please try again.", - "cash": "Cash", - "approved": "Approved", - "approvalRejectedPleaseVisitReceptionist": "Approval Rejected - Please visit receptionist", - "sentForApproval": "Sent For Approval", - "ancillaryOrderDetails": "Ancillary Order Details", - "noProceduresAvailableForSelectedOrder": "No Procedures available for the selected order.", - "procedures": "Procedures", - "totalAmount": "Total Amount", - "covered": "Covered", - "vatPercent": "VAT (15%)", - "proceedToPayment": "Proceed to Payment", - "supportedSmartWatches": "Supported Smart Watches", - "pleaseMakeSureSamsungWatchConnected": "Please make sure that your Samsung Watch is connected to your Phone, is actively synced & updated.", - "beforeSyncingDataFollowInstructions": "Before syncing data, please make sure that you have followed the instructions properly.", - "viewWatchInstructions": "View watch instructions", - "healthConnectAppNotInstalled": "Seems like you do not have Health Connect App installed. Please install it from the Play Store to sync your health data.", - "setTimerOfReminder": "Set the timer of reminder", - "youHaveAppointmentWithDr": "You have appointment with Dr. ", - "hours": "Hours", - "secs": "Secs", - "noAllergiesDataFound": "No allergies data found...", - "heartRateDescription": "Your heart rate indicates how many times your heart beats per minute", - "bloodOxygenDescription": "Blood oxygen level indicates how much oxygen your red blood cells are carrying", - "stepsDescription": "Number of steps taken throughout the day", - "caloriesDescription": "Calories burned during physical activity", - "distanceDescription": "Distance covered throughout the day", - "overview": "Overview", - "details": "Details", - "healthy": "Healthy", - "warning": "Warning", - "vitalSignDetails": "Vital Sign Details", - "resultOfNoDate": "Result of --", - "referenceRangeBetween": "Reference range: {low} – {high} {unit}", - "referenceRangeMin": "Reference range: ≥ {low} {unit}", - "referenceRangeMax": "Reference range: ≤ {high} {unit}", - "noHistoryAvailable": "No history available", - "bmiDescription": "BMI is a measurement based on height and weight that estimates body fat.", - "heightDescription": "Height is measured in centimeters and is used to calculate BMI and dosage recommendations.", - "weightDescription": "Weight helps track overall health, nutrition, and changes over time.", - "bloodPressureDescription": "Blood pressure reflects the force of blood against artery walls. It is shown as systolic/diastolic.", - "temperatureDescription": "Body temperature reflects how hot your body is and may change with infection or inflammation.", - "heartRateDescriptionVital": "Heart rate refers to the number of heart beats per minute.", - "respiratoryRateDescription": "Respiratory rate is the number of breaths taken per minute.", - "bmiAdvice": "Maintain a balanced diet and regular activity. If your BMI is high or low, consider consulting your doctor.", - "heightAdvice": "No action is needed unless your measurement looks incorrect. Update it during your next visit.", - "weightAdvice": "Monitor weight changes. Sudden gain or loss may require medical advice.", - "bloodPressureAdvice": "Keep tracking your blood pressure. High or low readings should be discussed with your doctor.", - "temperatureAdvice": "If you have a persistent fever or symptoms, contact your healthcare provider.", - "heartRateAdvice": "Track your heart rate trends. If you feel dizziness or chest pain, seek medical care.", - "respiratoryRateAdvice": "If you notice shortness of breath or abnormal breathing, seek medical advice.", - "whatShouldIDoNext": "What should I do next?", - "customizeDrinkCup": "Customize your drink cup", - "tipsToStayHydrated": "Tips to stay hydrated", - "drinkBeforeYouFeelThirsty": "Drink before you feel thirsty", - "keepRefillableBottleNextToYou": "Keep a refillable bottle next to you", - "trackYourDailyIntakeToStayMotivated": "Track your daily intake to stay motivated", - "chooseSparklingWaterInsteadOfSoda": "Choose sparkling water instead of soda", - "switchCup": "Switch Cup", - "plainWater": "Plain Water", - "yourGoal": "Your Goal", - "remaining": "Remaining", - "hydrationStatus": "Hydration Status", - "areYouSureYouWantToCancelAllWaterReminders": "Are you sure you want to cancel all water reminders?", - "remindersSet": "Reminders Set!", - "dailyWaterRemindersScheduledAt": "Daily water reminders scheduled at:", - "waterConsumption": "Water Consumption", - "selectNumberOfReminders": "Select Number of Reminders", - "h2oSettings": "H20 Settings", - "settingsSavedSuccessfully": "Settings saved successfully", - "yourName": "Your Name", - "ageYears": "Age (11-120) yrs", - "numberOfRemindersInADay": "Number of reminders in a day", - "medications": "Medications", - "remarks": "Remarks: ", - "someRemarksAboutPrescription": "some remarks about the prescription will be here", - "notifyMeBeforeConsumptionTime": "Notify me before the consumption time", - "noMedicationsToday": "No medications today", - "route": "Route", - "frequency": "Frequency", - "reminders": "Reminders", - "reminderAddedToCalendar": "Reminder added to calendar ✅", - "errorWhileSettingCalendar": "Error while setting calendar: {error}", - "instructions": "Instructions", - "requests": "Requests", - "thisWeek": "This Week", - "lastMonth": "Last Month", - "lastSixMonths": "Last 6 Months", - "selectTime": "Select Time", - "pleaseWaitYouWillBeCalledForVitalSigns": "Please wait! you will be called for vital signs", - "pleaseVisitRoomForVitalSigns": "Please visit Room {roomNumber} for vital signs", - "pleaseVisitRoomToTheDoctor": "Please visit Room {roomNumber} to the Doctor", - "erOnlineCheckInRequest": "ER Online Check-In Request", - "indoor": "Indoor", - "navigation": "Navigation", - "health": "Health", - "calculators": "Calculators", - "converters": "Converters", - "guide": "Guide", - "liveCareServiceDesc": "Get medical advice with a specialist doctor Via a video call", - "bloodGroup": "Blood: {bloodType}", - "medicalServices": "Medical Services", - "insuranceAndPayments": "Insurance & Payments", - "trackerAndOthers": "Tracker & Others", - "bmi": "BMI", - "yesPleaseINeedHelp": "Yes, Please I need help", - "confirmYourLocation": "Confirm Your Location", - "confirmYourLocationDesc": "Please confirm your present location to view appropriate directions.", - "insideHospital": "I am inside the hospital", - "outsideHospital": "I am outside the hospital", - "servicePriceList": "Services Price List", - "servicePriceListDesc": "Below is the services price list outline the healthcare services fees for cash payments, where the insurance coverage, eligibility, and co-payment deductions will be processed in accordance with the insurance policy terms and the table of benefits of each insurance providers:", - "servicePriceListRights": "The patient has the right to a free follow-up within 14 days of initial visit", - "serviceName": "Service Name", - "noFreeSlot": "No free slot available for the selected date. Please select another date or check back later.", - "noThanksIKnowTheClinic": "No, Thanks. I Know the clinic", - "unableToSendOTP": "Unable to send Verification OTP", - "aiOverView": "AI Overview", - "generateAiAnalysis": "Generate AI analysis for this order", - "aiAnalysis": "AI Analysis", - "prediction": "Prediction", - "prevention": "Prevention", - "disclaimer": "Disclaimer", - "thisAboveInfo": "The above information is generated by AI based on your lab results and is intended for informational purposes only. It should not be considered a medical diagnosis or treatment plan. Always consult with your healthcare provider for personalized medical advice and before making any health-related decisions.", - "aiDisclaimer": "Your lab result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?", - "consent": "Consent", - "bmiNormalDescription": "Your BMI is within the healthy range. Continue a balanced diet, regular physical activity, and routine checkups to maintain good health. Monitor any significant changes over time.", - "bmiUnderweightDescription": "BMI suggests you are underweight. Consider increasing calorie intake with nutrient-dense foods, include resistance training to build muscle, and consult a healthcare professional to check for underlying causes.", - "bmiOverweightDescription": "BMI indicates overweight. Aim for gradual, sustainable weight loss through a modest calorie deficit, regular physical activity, and healthier food choices. Seek medical advice before major lifestyle changes.", - "caloriesMaintenanceDescription": "Estimated daily calories to maintain your current weight given your activity level and metabolism. Use this as a baseline: increase slightly for muscle gain, or create a small deficit for gradual weight loss. Track progress and adjust every 2–4 weeks.", - "caloriesModerateDeficitDescription": "A moderate calorie reduction designed for steady, sustainable weight loss (typically 0.25–0.5 kg per week). Combine with resistance training and adequate protein to preserve muscle. Avoid extreme cuts and consult a professional if you have medical conditions.", - "caloriesAggressiveDeficitDescription": "A large calorie reduction that may produce faster weight loss but can be hard to sustain and increase risk of nutrient deficiencies, fatigue, or muscle loss. Not recommended long-term; seek guidance from a healthcare or nutrition professional before continuing.", - "bodyFatEssentialDescription": "Your body fat percentage is in the essential/athletic range. This level is typical for athletes and indicates very low body fat. While this may be optimal for performance, ensure you maintain adequate nutrition and hormone function. Consult a healthcare professional if you experience fatigue, hormonal issues, or other health concerns.", - "bodyFatFitnessDescription": "Your body fat percentage is in the fitness range. This indicates a healthy level of body fat associated with good physical conditioning. Maintain your current lifestyle habits including regular exercise and balanced nutrition to sustain this healthy range.", - "bodyFatAverageDescription": "Your body fat percentage is in the average/acceptable range. This is considered a healthy range for general population. To improve body composition, consider incorporating strength training, cardiovascular exercise, and a balanced diet. Focus on gradual, sustainable changes for long-term health.", - "bodyFatObeseDescription": "Your body fat percentage is above the recommended healthy range. This may increase health risks including cardiovascular disease, diabetes, and joint problems. Consider consulting a healthcare provider or registered dietitian to develop a personalized plan that includes balanced nutrition, regular physical activity, and lifestyle modifications for sustainable weight management.", - "bmrNormalDescription": "Your Basal Metabolic Rate (BMR) is within the expected range for your profile. BMR is the energy your body needs at rest to maintain vital functions (breathing, circulation, temperature). Use this value as the baseline for estimating total daily energy needs by applying an activity multiplier. Maintain a balanced diet and regular physical activity to support metabolic health.", - "bmrLowDescription": "Your BMR is lower than typical for your profile. Possible causes include lower lean muscle mass, age-related metabolic decline, or metabolic adaptation from prolonged calorie restriction. Consider focusing on resistance training to build/maintain muscle, ensure adequate protein and micronutrient intake, and consult a healthcare professional if this is unexpected.", - "bmrHighDescription": "Your BMR is higher than average for your profile. This can reflect greater lean mass, younger age, or a naturally higher metabolic rate, meaning you burn more energy at rest. Use this information to tailor calorie targets: higher needs for maintenance or muscle gain, moderate deficit for weight loss. Seek medical advice if you notice rapid unexplained changes.", - "ibwAtTargetDescription": "Your weight is at the ideal target. Maintain your current habits — balanced diet and regular activity — to keep this.", - "ibwBelowTargetDescription": "You are below the ideal weight by {kg} kg. Aim to gradually increase weight with a modest calorie surplus, nutrient-dense foods, and resistance training.", - "ibwAboveTargetDescription": "You are above the ideal weight by {kg} kg. Aim for a gradual, sustainable weight reduction through a moderate calorie deficit, increased activity, and balanced nutrition.", - "ibwCloseToRangeDescription": "Your weight is close to the ideal range. Maintain a balanced diet and regular activity; follow personalized advice from a healthcare professional if needed.", - "ibwUnderweightDescription": "You are below the ideal weight by {kg} kg. Focus on increasing calorie intake with nutrient-dense foods, prioritize protein and resistance training to build healthy mass, and consider consulting a healthcare professional.", - "ibwUnderweightGenericDescription": "You appear underweight compared to the ideal. Consider increasing calorie intake with nutrient-dense foods and resistance training; seek professional guidance if needed.", - "ibwOverweightDescription": "You are above the ideal weight by {kg} kg. Aim for a gradual, sustainable weight loss strategy — moderate calorie deficit, regular physical activity, and balanced nutrition. Consult a professional before major changes.", - "ibwOverweightGenericDescription": "You appear above the ideal weight. Consider a gradual, sustainable calorie deficit combined with activity and balanced nutrition; consult a professional if needed.", - "dietVeryLowCarbsDescription": "Very low‑carb (ketogenic) approach: minimal carbohydrates, higher fat and moderate protein. May support rapid weight loss and improved blood sugar control for some, but can be hard to sustain and may cause nutrient gaps. Monitor hydration/electrolytes and consult a professional for long‑term use.", - "dietLowCarbsDescription": "Low‑carb, higher‑protein plan: reduced carbohydrates with increased protein to support satiety and muscle maintenance. Helpful for weight management and glycemic control when balanced with vegetables and healthy fats. Ensure adequate fiber and micronutrients.", - "dietModerateCarbsDescription": "Moderate carbohydrate intake with balanced protein and fats: provides steady energy for daily activity and exercise while allowing flexibility. Suitable for many people aiming for sustainable weight management and performance.", - "dietUSDAGuidelinesDescription": "USDA‑based balanced pattern: emphasizes fruits, vegetables, whole grains, lean proteins, and limited added sugars and saturated fats. Evidence‑based framework for general health, nutrient adequacy, and chronic disease prevention.", - "dietZoneDietDescription": "Zone Diet (~40% carbs / 30% protein / 30% fat): emphasizes hormonal balance and portion control. May improve body composition and energy for some, but requires planning; personalize with a nutrition professional.", - "contextDependent": "Context Dependent", - "calculatedValue": "Calculated Value", - "calculationFormula": "Calculation Formula", - "loadingAIAnalysis": "Analysing your results, Please be patient and let the AI do the magic, This might take some time.", - "symptoms": "Symptoms", - "insuranceInActive": "Insurance Inactive", - "insuranceInActiveContactSupport": "You have a valid insurance but it is currently inactive in HMG. Please contact HMG customer support to activate it.", - "updateAppNow": "Update App Now", - "Dr": "Dr.", - "reschedulingAppo": "Rescheduling Appointment, Please wait...", - "invalidEligibility": "You cannot make online payment because you are not eligible to use the provided service.", - "invalidInsurance": "You cannot make online payment because you do not have a valid insurance.", - "continueCash": "Continue as cash", - "applewatch": "Apple Watch", - "applehealthapplicationshouldbeinstalledinyourphone": "Apple Health application should be installed in your phone", - "unabletodetectapplicationinstalledpleasecomebackonceinstalled": "We are unable to detect the application installed in your device. Please come back here once you have installed this application.", - "applewatchshouldbeconnected": "Apple Watch should be connected", - "samsungwatch": "Samsung Watch", - "samsunghealthapplicationshouldbeinstalledinyourphone": "Samsung Health application should be installed in your phone", - "samsungwatchshouldbeconnected": "Samsung Watch should be connected", - "huaweiwatch": "Huawei Watch", - "huaweihealthapplicationshouldbeinstalledinyourphone": "Huawei Health application should be installed in your phone", - "huaweiwatchshouldbeconnected": "Huawei Watch should be connected", - "whoopwatch": "Whoop Watch", - "whoophealthapplicationshouldbeinstalledinyourphone": "Whoop Health application should be installed in your phone", - "whoopwatchshouldbeconnected": "Whoop Watch should be connected", - "updatetheinformation": "This will allow to gather the most up to date information from your apple watch", - "timeFor": "Time For", - "hmgPolicies": "HMG Policies", - "featureComingSoonDescription": "Feature is coming soon. We are actively working to bring more exciting features into the app. Stay tuned for updates.", - "darkMode": "Dark Mode", - "generateAiAnalysisResult": "Generate AI analysis for this result", - "ratings": "Ratings", - "hmgPharmacyText": "Al Habib Pharmacy, the complete online Pharmaceutical store brought to you by Dr. Sulaiman Al Habib Medical Services Group.", - "insuranceRequestSubmittedSuccessfully": "Your insurance update request has been successfully submitted. You will be notified once completed.", - "updatingEmailAddress": "Updating email address, Please wait...", - "verifyInsurance": "Verify Insurance", - "tests": "tests", - "calendarPermissionAlert": "Please grant calendar access permission from app settings to set medication reminder.", - "timeForFirstReminder": "Time for 1st reminder", - "reminderRemovalNote": "You can remove it from your calendar later by switching off the reminder", - "sortByLocation": "Sort by location", - "sortByNearestLocation": "Sort by nearest to your location", - "giveLocationPermissionForNearestList": "Please grant location permission from app settings to see the nearest locations.", - "communicationLanguage": "Communication Language", - "cmcServiceHeader": "Complete Health Checkup: Advanced diagnostics, Detailed Health insights", - "cmcServiceDescription": "Get detailed insights into your health with our advanced diagnostics. Understand your body better for a healthier future.", - "eReferralServiceHeader": "HMG Hospital E-Referral: Streamlined patient referrals", - "eReferralServiceDescription": "Facilitate seamless patient transfers to HMG with our secure e-referral system. Ensure continuity of care for every patient.", - "bloodDonationServiceHeader": "Give Blood, Save Lives. Your donation makes a difference.", - "bloodDonationServiceDescription": "Donate blood, empower lives. Your contribution creates hope. Join our blood drive and be a lifeline for those in need. Every drop counts!", - "healthTrackersServiceHeader": "Track Your Vitals with Ease and effortlessly ", - "healthTrackersServiceDescription": "Input your metrics for continuous heart rate monitoring, blood pressure and precise daily activity summaries. Stay informed and optimize your well-being with ease.", - "waterConsumptionServiceHeader": "Stay Hydrated, Stay Healthy. Track your daily water intake with ease.", - "waterConsumptionServiceDescription": "Quench your thirst, track your health. Effortlessly monitor your daily water intake with our user-friendly app, ensuring optimal hydration and well-being.", - "smartWatchServiceHeader": "Sync Your Smartwatch with Health Apps", - "smartWatchServiceDescription": "Seamlessly connect your smartwatch to our health app for effortless data tracking and personalized insights.", - "liveChatServiceHeader": "24/7 Expert\nAssistance", - "liveChatServiceDescription": "Need help ? Our live chat connects you with expert support for any questions about features, settings, or troubleshooting.", - "emergencyServiceHeader": "ER Check-in, Faster Than Ever. Call ambulance / Rapid Response Team instantly", - "emergencyServiceDescription": "In a medical emergency? Our ambulances and rapid response teams are on standby 24/7. Plus, quick ER check-in for faster care.", - "homeHealthCareServiceHeader": "Your Health, Elevated. Premium care, delivered to your doorstep.", - "homeHealthCareServiceDescription": "We bring quality healthcare to your doorstep. Experienced nurses providing compassionate care in the comfort of your home.", - "profileOnlyText": "Profile", - "information": "Information", - "noFavouriteDoctors": "You don't have any favourite list yet", - "addDoctors": "Add Doctors", - "favouriteList": "Favourite List", - "later": "Later", - "cancelAppointmentConfirmMessage": "Are you sure you want to cancel this appointment?", - "acknowledged": "Acknowledged", - "searchLabResults": "Search lab results", - "callForAssistance": "Call for immediate assistance", - "oneWaySubtitle": "Pickup from location to hospital", - "twoWaySubtitle": "Round trip from location to hospital and back", - "toHospitalSubtitle": "Transfer from your current location to the hospital", - "fromHospitalSubtitle": "Transfer from the hospital back to your home", - "clinicSchedule": "Clinic Schedule", - "doctorAddedToFavourite": "Added to favorites list", - "doctorRemovedFromFavourite": "Removed from favorites list", - "favourites": "Favourites", - "addNewFavorite": "Add new favourite", - "pendingAncillaryOrders": "Pending ancillary orders", - "liveCarePermissions": "LiveCare requires Camera, Microphone & Notifications permissions to enable virtual consultation between patient & doctor, Open app settings to allow them.", - "containsImage": "Contains Image", - "servicesSelected": "Service(s) Selected", - "selectMedicalFileFromFamily": "Select a medical file from your family", - "anyFileFromHMG": "Any active medical file from HMG", - "noDistance": "Distance not available", - "enterAmount": "Please enter amount to continue.", - "selectHospitalContinue": "Please select hospital to continue.", - "year": "Year", - "notifyPrescription": "Notify me before the consumption time", - "doctorRatingAppointment": "Doctor\nRating", - "ratingSubmitted": "Rating Submitted", - "rateTheDoctor": "Please rate the doctor", - "downloadInvoice": "Download Invoice", - "approvalDetails": "Approval Details", - "obese": "Obese", - "overWeight": "OverWeight", - "underWeight": "UnderWeight", - "nearestGate": "Nearest Gate", - "RRTDDetails": "The Rapid response team provides a comprehensive medical service for all sorts of urgent and stable cases of all ages, including adult and paediatric patients.", - "normalMapType": "Default", - "satelliteMapType": "Satellite", - "terrainMapType": "Terrain", - "hybridMapType": "Hybrid", - "openCamera": "Open Camera", - "openGallery": "Open Gallery", - "openFiles": "Open Files", - "generateAiAnalysisRadResult": "Generate AI analysis for this result", - "grantLocationPermission": "Please grant location permission from app settings to see better results.", - "NoBookedAppointments": "No Booked Appointments", - "aiDisclaimerRad": "Your radiology result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?", - "imageSizeTooLarge": "Image size is too large. Maximum size is 1MB", - "profileImageUpdatedSuccessfully": "Profile image updated successfully", - "failedToProcessImage": "Failed to process image. Please try again", - "cameraGalleryPermissionMessage": "Camera and Gallery permissions are required to upload profile picture. Please grant permissions from app settings.", - "cameraGalleryPermissionRequired": "Camera and Gallery permissions are required", - "cameraPermissionMessage": "Camera permission is required to take photos. Please grant permission from app settings.", - "cameraPermissionRequired": "Camera permission is required", - "galleryPermissionMessage": "Gallery permission is required to select photos. Please grant permission from app settings.", - "galleryPermissionRequired": "Gallery permission is required", - "failedToCheckPermissions": "Failed to check permissions. Please try again", - "takePhotoWithCamera": "Take a photo with your camera", - "selectFromPhotoLibrary": "Select from your photo library", - "selectDocumentFiles": "Select document files", - "profilePicture": "Profile Picture", - "sendingAskDrRequest": "Sending Request...", - "askDrRequestSentSuccess": "Request has been sent successfully, The doctor will get back to you soon.", - "onlineCheckIn24HoursOnly": "Online Check-In is only available within 4 hours of the appointment time." -}; -static const Map> mapLocales = {"ar_SA": _ar_SA, "en_US": _en_US}; -} diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 1bd8019d..22cca7e4 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -87,6 +87,8 @@ abstract class LocaleKeys { static const bookSuccessConfirmMore2412 = 'bookSuccessConfirmMore2412'; static const upcomingPaymentPending = 'upcomingPaymentPending'; static const upcomingPaymentNow = 'upcomingPaymentNow'; + static const timeUntilAppointment = 'timeUntilAppointment'; + static const appointmentTimePassed = 'appointmentTimePassed'; static const upcomingQR = 'upcomingQR'; static const upcomingVirtual = 'upcomingVirtual'; static const upcomingLivecare = 'upcomingLivecare'; @@ -198,6 +200,7 @@ abstract class LocaleKeys { static const enterEmail = 'enterEmail'; static const family = 'family'; static const familyTitle = 'familyTitle'; + static const familyTitle2 = 'familyTitle2'; static const myFamily = 'myFamily'; static const addNewMember = 'addNewMember'; static const sentRequests = 'sentRequests'; @@ -270,6 +273,7 @@ abstract class LocaleKeys { static const userViewRequester = 'userViewRequester'; static const userView = 'userView'; static const parking = 'parking'; + static const parking2 = 'parking2'; static const alhabiServices = 'alhabiServices'; static const parkingTitle = 'parkingTitle'; static const parkingDescription = 'parkingDescription'; @@ -308,13 +312,16 @@ abstract class LocaleKeys { static const vitalSignObese = 'vitalSignObese'; static const vitalSignOverweight = 'vitalSignOverweight'; static const vitalSignUnderweight = 'vitalSignUnderweight'; + static const vitalSignSkinny = 'vitalSignSkinny'; static const myMedical = 'myMedical'; static const myMedicalSubtitle = 'myMedicalSubtitle'; static const myDoctor = 'myDoctor'; static const myDoctorSubtitle = 'myDoctorSubtitle'; + static const myDoctorSubtitle2 = 'myDoctorSubtitle2'; static const eye = 'eye'; static const eyeSubtitle = 'eyeSubtitle'; static const insurance = 'insurance'; + static const insurance2 = 'insurance2'; static const insuranceSubtitle = 'insuranceSubtitle'; static const updateInsurance = 'updateInsurance'; static const updateInsuranceSubtitle = 'updateInsuranceSubtitle'; @@ -329,6 +336,7 @@ abstract class LocaleKeys { static const monthly = 'monthly'; static const monthlySubtitle = 'monthlySubtitle'; static const sick = 'sick'; + static const sick2 = 'sick2'; static const sickSubtitle = 'sickSubtitle'; static const myBalance = 'myBalance'; static const myBalanceSubtitle = 'myBalanceSubtitle'; @@ -492,8 +500,10 @@ abstract class LocaleKeys { static const refferalSubTitle = 'refferalSubTitle'; static const healthCare = 'healthCare'; static const emergency = 'emergency'; + static const emergency2 = 'emergency2'; static const erservices = 'erservices'; static const services2 = 'services2'; + static const services3 = 'services3'; static const cantSeeProfile = 'cantSeeProfile'; static const loginRegisterNow = 'loginRegisterNow'; static const hmgPharmacy = 'hmgPharmacy'; @@ -764,6 +774,7 @@ abstract class LocaleKeys { static const erConsultation = 'erConsultation'; static const myInvoice = 'myInvoice'; static const invoiceList = 'invoiceList'; + static const invoiceList2 = 'invoiceList2'; static const allInvoices = 'allInvoices'; static const hospitals = 'hospitals'; static const clinics = 'clinics'; @@ -821,7 +832,9 @@ abstract class LocaleKeys { static const switchAccount = 'switchAccount'; static const lastLoginBy = 'lastLoginBy'; static const allSet = 'allSet'; + static const quickLogin = 'quickLogin'; static const enableQuickLogin = 'enableQuickLogin'; + static const enableQuickLoginBtn = 'enableQuickLoginBtn'; static const enableMsg = 'enableMsg'; static const notNow = 'notNow'; static const pendingActivation = 'pendingActivation'; @@ -883,6 +896,7 @@ abstract class LocaleKeys { static const monitor = 'monitor'; static const noSpecialResult = 'noSpecialResult'; static const setTheDateRange = 'setTheDateRange'; + static const selectDate = 'selectDate'; static const historyFlowchart = 'historyFlowchart'; static const to = 'to'; static const startDate = 'startDate'; @@ -1144,11 +1158,18 @@ abstract class LocaleKeys { static const recharge = 'recharge'; static const activityLevel = 'activityLevel'; static const selectActivityLevel = 'selectActivityLevel'; + static const almostInactive = 'almostInactive'; + static const lightlyActive = 'lightlyActive'; + static const lightlyActive13Days = 'lightlyActive13Days'; + static const superActive = 'superActive'; static const caloriesPerDay = 'caloriesPerDay'; static const dietType = 'dietType'; static const selectDietType = 'selectDietType'; static const bodyFrameSize = 'bodyFrameSize'; static const selectBodyFrameSize = 'selectBodyFrameSize'; + static const bodyFrameSmall = 'bodyFrameSmall'; + static const bodyFrameMedium = 'bodyFrameMedium'; + static const bodyFrameLarge = 'bodyFrameLarge'; static const averageCycleLength = 'averageCycleLength'; static const averageLutealPhase = 'averageLutealPhase'; static const convert = 'convert'; @@ -1174,6 +1195,14 @@ abstract class LocaleKeys { static const carbsProteinFat = 'carbsProteinFat'; static const ovulationPeriod = 'ovulationPeriod'; static const deliveryDueDate = 'deliveryDueDate'; + static const bmiCalculatorDesc = 'bmiCalculatorDesc'; + static const caloriesCalculatorDesc = 'caloriesCalculatorDesc'; + static const bmrCalculatorDesc = 'bmrCalculatorDesc'; + static const idealBodyWeightDesc = 'idealBodyWeightDesc'; + static const bodyFatCalculatorDesc = 'bodyFatCalculatorDesc'; + static const carbsProteinFatDesc = 'carbsProteinFatDesc'; + static const ovulationPeriodDesc = 'ovulationPeriodDesc'; + static const deliveryDueDateDesc = 'deliveryDueDateDesc'; static const low = 'low'; static const preDiabetic = 'preDiabetic'; static const high = 'high'; @@ -1218,6 +1247,7 @@ abstract class LocaleKeys { static const hmgServices = 'hmgServices'; static const personalServices = 'personalServices'; static const habibWallet = 'habibWallet'; + static const habibWallet2 = 'habibWallet2'; static const loginToViewWalletBalance = 'loginToViewWalletBalance'; static const loginToViewMedicalFile = 'loginToViewMedicalFile'; static const addMember = 'addMember'; @@ -1307,6 +1337,7 @@ abstract class LocaleKeys { static const askYourDoctor = 'askYourDoctor'; static const internetPairing = 'internetPairing'; static const requested = 'requested'; + static const readyToDownload = 'readyToDownload'; static const youDontHaveAnyMedicalReportsYet = 'youDontHaveAnyMedicalReportsYet'; static const requestMedicalReport = 'requestMedicalReport'; static const youDoNotHaveAnyAppointmentsToRequestMedicalReport = 'youDoNotHaveAnyAppointmentsToRequestMedicalReport'; @@ -1451,6 +1482,8 @@ abstract class LocaleKeys { static const setTimerOfReminder = 'setTimerOfReminder'; static const youHaveAppointmentWithDr = 'youHaveAppointmentWithDr'; static const hours = 'hours'; + static const minutes = 'minutes'; + static const seconds = 'seconds'; static const secs = 'secs'; static const noAllergiesDataFound = 'noAllergiesDataFound'; static const heartRateDescription = 'heartRateDescription'; @@ -1460,6 +1493,7 @@ abstract class LocaleKeys { static const distanceDescription = 'distanceDescription'; static const overview = 'overview'; static const details = 'details'; + static const details2 = 'details2'; static const healthy = 'healthy'; static const warning = 'warning'; static const vitalSignDetails = 'vitalSignDetails'; @@ -1522,7 +1556,7 @@ abstract class LocaleKeys { static const pleaseVisitRoomToTheDoctor = 'pleaseVisitRoomToTheDoctor'; static const erOnlineCheckInRequest = 'erOnlineCheckInRequest'; static const indoor = 'indoor'; - static const navigation = 'navigation'; + static const thehospital = 'thehospital'; static const health = 'health'; static const calculators = 'calculators'; static const converters = 'converters'; @@ -1711,5 +1745,47 @@ abstract class LocaleKeys { static const sendingAskDrRequest = 'sendingAskDrRequest'; static const askDrRequestSentSuccess = 'askDrRequestSentSuccess'; static const onlineCheckIn24HoursOnly = 'onlineCheckIn24HoursOnly'; + static const searchFor = 'searchFor'; + static const m = 'm'; + static const inch = 'inch'; + static const updateEmailAddressTitle = 'updateEmailAddressTitle'; + static const enterNewNewContactNumber = 'enterNewNewContactNumber'; + static const nationalIdFileNumber = 'nationalIdFileNumber'; + static const finda = 'finda'; + static const errorFetchingData = 'errorFetchingData'; + static const negative = 'negative'; + static const calculateCaloriesBasedOnFactors = 'calculateCaloriesBasedOnFactors'; + static const conformationCall = 'conformationCall'; + static const radiologyLabResults = 'radiologyLabResults'; + static const disclaimerRadiology = 'disclaimerRadiology'; + static const enterIdentificationNumberWithNoStar = 'enterIdentificationNumberWithNoStar'; + static const enterReferralNumber = 'enterReferralNumber'; + static const referralNumber = 'referralNumber'; + static const phoneHint = 'phoneHint'; + static const selectSearchCriteria = 'selectSearchCriteria'; + static const selectCriteria = 'selectCriteria'; + static const locationServicesDisabled = 'locationServicesDisabled'; + static const selectDateTimeKey = 'selectDateTimeKey'; + static const medicalKey = 'medicalKey'; + static const convertBloodcholesterolInfo = 'convertBloodcholesterolInfo'; + static const carbsAlert = 'carbsAlert'; + static const bodyFatAlert = 'bodyFatAlert'; + static const ibwAlert = 'ibwAlert'; + static const glucoseAlert = 'glucoseAlert'; + static const selectBloodGroup = 'selectBloodGroup'; + static const routePrescription = 'routePrescription'; + static const noNearestER = 'noNearestER'; + static const loginToUseServiceLiveCare = 'loginToUseServiceLiveCare'; + static const mldl = 'mldl'; + static const mmol = 'mmol'; + static const triglycerideInfo = 'triglycerideInfo'; + static const selectSmartWatch = 'selectSmartWatch'; + static const selectBloodDonationCity = 'selectBloodDonationCity'; + static const selectBloodDonationGender = 'selectBloodDonationGender'; + static const selectBloodType = 'selectBloodType'; + static const doctors2 = 'doctors2'; + static const illurgyInfomation = 'illurgyInfomation'; + static const vaccineInfomation = 'vaccineInfomation'; + static const approvals1 = 'approvals1'; } diff --git a/lib/main.dart b/lib/main.dart index e9b599c9..23f7c4d7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,6 +10,7 @@ import 'package:flutter/services.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/utils.dart'; +import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; @@ -52,6 +53,7 @@ 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/theme/app_theme.dart'; import 'package:hmg_patient_app_new/theme/colors.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'; @@ -242,6 +244,11 @@ void main() async { ), ChangeNotifierProvider( create: (_) => getIt.get(), + ) , + ChangeNotifierProvider( + create: (_) => getIt.get(), + ), ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), // Wrap your app diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index ef3b0c94..dfdde83a 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import 'dart:ui' as ui; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -33,13 +34,11 @@ import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointmen import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_doctor_card.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_rating_widget.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor_request_type_select.dart'; -import 'package:hmg_patient_app_new/presentation/ask_doctor/ask_doctor_page.dart'; import 'package:hmg_patient_app_new/presentation/ask_doctor/doctor_response_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; +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/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart'; -import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -47,6 +46,7 @@ import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:lottie/lottie.dart'; import 'package:map_launcher/map_launcher.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; @@ -74,8 +74,19 @@ class _AppointmentDetailsPageState extends State { bool isAppointmentWithin4Hours = false; + // Countdown timer variables + Timer? _countdownTimer; + Duration? _timeRemaining; + String _countdownText = ""; + Function(void Function())? _modalSetState; // Callback for modal state updates + + // GlobalKey for BellAnimatedSwitch to control it programmatically + final GlobalKey _bellSwitchKey = GlobalKey(); + @override void initState() { + super.initState(); + scheduleMicrotask(() async { if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) { myAppointmentsViewModel.getDoctorsRatingCheck( @@ -90,9 +101,150 @@ class _AppointmentDetailsPageState extends State { } else { isAppointmentWithin4Hours = true; } + + // // Check if reminder exists in calendar and update the flag + } + }); + + // Initialize countdown timer for payment after first frame to ensure context is available + WidgetsBinding.instance.addPostFrameCallback((_) async { + if (mounted) { + _startCountdownTimer(); + // await _checkAndUpdateReminderStatus(); + } + }); + } + + /// Checks if a reminder exists in the calendar for this appointment + /// and updates the hasReminder flag accordingly + Future _checkAndUpdateReminderStatus() async { + try { + CalenderUtilsNew calender = CalenderUtilsNew.instance; + + // Check if reminder exists for this appointment + bool reminderExists = await calender.checkIfEventExist( + widget.patientAppointmentHistoryResponseModel.appointmentNo.toString() ?? "", + ); + + // Update the flag if it's different from current state + if (mounted && widget.patientAppointmentHistoryResponseModel.hasReminder != reminderExists) { + setState(() { + widget.patientAppointmentHistoryResponseModel.hasReminder = reminderExists; + }); + + // Also update in view model to keep data consistent + myAppointmentsViewModel.setAppointmentReminder(reminderExists, widget.patientAppointmentHistoryResponseModel); + } + + debugPrint("Reminder status for appointment ${widget.patientAppointmentHistoryResponseModel.appointmentNo}: $reminderExists"); + setState(() {}); + } catch (e) { + debugPrint("Error checking reminder status: $e"); + // If there's an error, default to false + if (mounted) { + setState(() { + widget.patientAppointmentHistoryResponseModel.hasReminder = false; + }); + } + } + } + + void _startCountdownTimer() { + final appointmentDate = DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate); + _updateTimeRemaining(); + + _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { + if (mounted) { + _updateTimeRemaining(); + } else { + timer.cancel(); } }); - super.initState(); + } + + void _updateTimeRemaining() { + final appointmentDate = DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate); + final now = DateTime.now(); + final difference = appointmentDate.difference(now); + + if (difference.isNegative) { + if (mounted) { + setState(() { + _timeRemaining = Duration.zero; + _countdownText = LocaleKeys.appointmentTimePassed.tr(context: context); + }); + } + // Also update modal if callback is available + _modalSetState?.call(() { + _timeRemaining = Duration.zero; + _countdownText = LocaleKeys.appointmentTimePassed.tr(context: context); + }); + _countdownTimer?.cancel(); + } else { + if (mounted) { + setState(() { + _timeRemaining = difference; + _countdownText = _formatDuration(difference); + }); + } + // Also update modal if callback is available + _modalSetState?.call(() { + _timeRemaining = difference; + _countdownText = _formatDuration(difference); + }); + } + } + + String _formatDuration(Duration duration) { + final days = duration.inDays; + final hours = duration.inHours.remainder(24); + final minutes = duration.inMinutes.remainder(60); + final seconds = duration.inSeconds.remainder(60); + + // Format: DD : HH : MM : SS + return '${days.toString().padLeft(2, '0')} : ${hours.toString().padLeft(2, '0')} : ${minutes.toString().padLeft(2, '0')} : ${seconds.toString().padLeft(2, '0')}'; + } + + @override + void dispose() { + _countdownTimer?.cancel(); + super.dispose(); + } + + // Helper method to build each time unit (number + label) + Widget _buildTimeUnit(String value, String label) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + alignment: Alignment.center, + child: value.toText32( + isBold: true, + color: AppColors.blackColor, + isEnglishOnly: true, + ), + ), + SizedBox(height: 4.h), + Container( + alignment: Alignment.center, + child: label.toText12( + color: AppColors.greyTextColor, + ), + ), + ], + ); + } + + // Helper method to build time separator (:) + Widget _buildTimeSeparator() { + return Padding( + padding: EdgeInsets.only(bottom: 16.h, left: 6.w, right: 6.w), + child: ':'.toText32( + isBold: true, + color: AppColors.blackColor, + ), + ); } @override @@ -196,11 +348,13 @@ class _AppointmentDetailsPageState extends State { }, onError: (err) { LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, + showCommonBottomSheetWithoutHeight(context, child: Utils.getErrorWidget(loadingText: err.toString()), callBackFunc: () {}, - title: "", isCloseButtonVisible: true, isDismissible: false, isFullScreen: false); + title: "", + isCloseButtonVisible: true, + isDismissible: false, + isFullScreen: false); }); }), callBackFunc: () {}, @@ -215,7 +369,193 @@ class _AppointmentDetailsPageState extends State { onRescheduleTap: () async { openDoctorScheduleCalendar(); }, + timerWidget: (_timeRemaining == null || _timeRemaining == Duration.zero) + ? null + : Column( + children: [ + + SizedBox(height: 16.w), + Divider(height: 1, color: AppColors.dividerColor), + SizedBox(height: 16.w), + Directionality( + textDirection: ui.TextDirection.ltr, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: _buildTimeUnit( + _timeRemaining!.inDays.toString().padLeft(2, '0'), + LocaleKeys.days.tr(context: context), + ), + ), + _buildTimeSeparator(), + Expanded( + child: _buildTimeUnit( + _timeRemaining!.inHours.remainder(24).toString().padLeft(2, '0'), + LocaleKeys.hours.tr(context: context), + ), + ), + _buildTimeSeparator(), + Expanded( + child: _buildTimeUnit( + _timeRemaining!.inMinutes.remainder(60).toString().padLeft(2, '0'), + LocaleKeys.minutes.tr(context: context), + ), + ), + _buildTimeSeparator(), + Expanded( + child: _buildTimeUnit( + _timeRemaining!.inSeconds.remainder(60).toString().padLeft(2, '0'), + LocaleKeys.seconds.tr(context: context), + ), + ), + ], + ), + ), + SizedBox(height: 16.h), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.prescription_reminder_icon, width: 40.w, height: 40.h, applyThemeColor: false), + SizedBox(width: 8.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4.h, + children: [ + LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), + LocaleKeys.notifyMeBeforeAppointment.tr(context: context).toText11(color: AppColors.textColorLight, isBold: true), + ], + ), + const Spacer(), + BellAnimatedSwitch( + key: _bellSwitchKey, + width: 56.w, + height: 38.h, + initialValue: widget.patientAppointmentHistoryResponseModel.hasReminder ?? false, + activeColor: AppColors.successColor.withOpacity(0.2), + inactiveColor: AppColors.lightGrayBGColor, + activeCircleColor: AppColors.successColor, + inactiveCircleColor: AppColors.greyTextColor, + activeIconColor: AppColors.bgGreenColor, + inactiveIconColor: AppColors.greyTextColor, + activeIcon: Utils.buildSvgWithAssets(icon: AppAssets.bell, iconColor: AppColors.whiteColor, width: 12.w, height: 12.h), + inactiveIcon: Utils.buildSvgWithAssets(icon: AppAssets.bell, iconColor: AppColors.whiteColor, width: 12.w, height: 12.h), + onChanged: (newValue) async { + CalenderUtilsNew calender = CalenderUtilsNew.instance; + bool isEventAddedOrRemoved = false; + if (newValue == true) { + DateTime startDate = DateTime.now(); + DateTime endDate = DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate); + + // Show reminder bottom sheet and check if permission was granted + bool permissionGranted = await BottomSheetUtils().showReminderBottomSheet( + context, + endDate, + widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "", + "${widget.patientAppointmentHistoryResponseModel.appointmentNo}" ?? "", + "", + "", + title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}", + description: + "${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}", + onSuccess: () { + setState(() { + myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); + }); + }, + isMultiAllowed: true, + onMultiDateSuccess: (int selectedIndex) async { + isEventAddedOrRemoved = await calender.createOrUpdateEvent( + title: + "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}", + description: + "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget.patientAppointmentHistoryResponseModel.projectName}", + scheduleDateTime: DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), + eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}", + location: '', + reminderMinutes: selectedIndex); + setState(() { + myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); + }); + }, + ); + + // If permission was not granted, revert the switch back to OFF + if (!permissionGranted) { + _bellSwitchKey.currentState?.setSwitchValue(false); + } + } else { + isEventAddedOrRemoved = await calender.checkAndRemove( + id: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}", + ); + setState(() { + myAppointmentsViewModel.setAppointmentReminder(!isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); + }); + } + }, + ) + + // Switch( + // activeThumbColor: AppColors.successColor, + // // activeTrackColor: AppColors.successColor.withValues(alpha: .15), + // value: widget.patientAppointmentHistoryResponseModel.hasReminder!, + // onChanged: (newValue) async { + // CalenderUtilsNew calender = CalenderUtilsNew.instance; + // bool isEventAddedOrRemoved = false; + // if(newValue == true){ + // DateTime startDate = DateTime.now(); + // DateTime endDate = DateUtil.convertStringToDate(widget + // .patientAppointmentHistoryResponseModel.appointmentDate); + // BottomSheetUtils().showReminderBottomSheet( + // context, + // endDate, + // widget.patientAppointmentHistoryResponseModel.doctorNameObj??"", + // "${widget.patientAppointmentHistoryResponseModel.appointmentNo}"??"", + // "", + // "", + // title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}", + // description: + // "${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}", + // onSuccess: () { + // setState(() { + // myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); + // }); + // }, + // isMultiAllowed: true, + // onMultiDateSuccess: (int selectedIndex) async { + // isEventAddedOrRemoved = await calender.createOrUpdateEvent( + // title: + // "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}", + // description: + // "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget.patientAppointmentHistoryResponseModel.projectName}", + // scheduleDateTime: DateUtil.convertStringToDate(widget + // .patientAppointmentHistoryResponseModel.appointmentDate), + // eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}", + // location: '', + // reminderMinutes: selectedIndex + // ); + // setState(() { + // myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); + // }); + // }, + // ); + // }else { + // isEventAddedOrRemoved = await calender.checkAndRemove( id:"${widget.patientAppointmentHistoryResponseModel.appointmentNo}", ); + // setState(() { + // myAppointmentsViewModel.setAppointmentReminder(!isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); + // }); + // } + // + // + // }, + // ), + ], + ).paddingSymmetrical(16.w, 0) + ], + ), ), + SizedBox(height: 16.h), !AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? Column( @@ -359,104 +699,172 @@ class _AppointmentDetailsPageState extends State { ], ), SizedBox(height: 8.h), - bookAppointmentsViewModel.appointmentNearestGateResponseModel != null ? Wrap( - direction: Axis.horizontal, - spacing: 8.w, - runSpacing: 8.h, - children: [ - AppCustomChipWidget( - labelText: bookAppointmentsVM.isAppointmentNearestGateLoading - ? "Floor: Ground Floor" + bookAppointmentsViewModel.appointmentNearestGateResponseModel != null + ? Wrap( + direction: Axis.horizontal, + spacing: 8.w, + runSpacing: 8.h, + children: [ + AppCustomChipWidget( + labelText: bookAppointmentsVM.isAppointmentNearestGateLoading + ? "Floor: Ground Floor" : "${LocaleKeys.floor.tr(context: context)}: ${getIt.get().isArabic() ? bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocationN : bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocation}", ).toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), - AppCustomChipWidget( - labelText: + AppCustomChipWidget( + labelText: "${LocaleKeys.nearestGate.tr(context: context)}: ${getIt.get().isArabic() ? bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumberN : bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumber}") .toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), - ], - ) : SizedBox.shrink(), + ], + ) + : SizedBox.shrink(), ], ), ), ); }), SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.r, - hasShadow: false, - ), - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.prescription_reminder_icon, width: 35.h, height: 35.h, applyThemeColor: false), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), - LocaleKeys.notifyMeBeforeAppointment.tr(context: context).toText11(color: AppColors.textColorLight, isBold: true), - ], - ), - const Spacer(), - Switch( - activeThumbColor: AppColors.successColor, - // activeTrackColor: AppColors.successColor.withValues(alpha: .15), - value: widget.patientAppointmentHistoryResponseModel.hasReminder!, - onChanged: (newValue) async { - CalenderUtilsNew calender = CalenderUtilsNew.instance; - bool isEventAddedOrRemoved = false; - if(newValue == true){ - DateTime startDate = DateTime.now(); - DateTime endDate = DateUtil.convertStringToDate(widget - .patientAppointmentHistoryResponseModel.appointmentDate); - BottomSheetUtils().showReminderBottomSheet( - context, - endDate, - widget.patientAppointmentHistoryResponseModel.doctorNameObj??"", - "${widget.patientAppointmentHistoryResponseModel.appointmentNo}"??"", - "", - "", - title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}", - description: - "${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}", - onSuccess: () { - setState(() { - myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); - }); - }, - isMultiAllowed: true, - onMultiDateSuccess: (int selectedIndex) async { - isEventAddedOrRemoved = await calender.createOrUpdateEvent( - title: - "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}", - description: - "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget.patientAppointmentHistoryResponseModel.projectName}", - scheduleDateTime: DateUtil.convertStringToDate(widget - .patientAppointmentHistoryResponseModel.appointmentDate), - eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}", - location: '', - reminderMinutes: selectedIndex - ); - setState(() { - myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); - }); - }, - ); - }else { - isEventAddedOrRemoved = await calender.checkAndRemove( id:"${widget.patientAppointmentHistoryResponseModel.appointmentNo}", ); - setState(() { - myAppointmentsViewModel.setAppointmentReminder(!isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); - }); - } - - - }, - ), - ], - ).paddingSymmetrical(16.h, 16.h), - ), + // Container( + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: AppColors.whiteColor, + // borderRadius: 20.r, + // hasShadow: false, + // ), + // child: Row( + // mainAxisSize: MainAxisSize.max, + // children: [ + // Utils.buildSvgWithAssets(icon: AppAssets.prescription_reminder_icon, width: 35.h, height: 35.h, applyThemeColor: false), + // SizedBox(width: 8.h), + // Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), + // LocaleKeys.notifyMeBeforeAppointment.tr(context: context).toText11(color: AppColors.textColorLight, isBold: true), + // ], + // ), + // const Spacer(), + // BellAnimatedSwitch( + // key: _bellSwitchKey, + // initialValue: widget.patientAppointmentHistoryResponseModel.hasReminder ?? false, + // activeColor: AppColors.successColor.withOpacity(0.2), + // inactiveColor: AppColors.lightGrayBGColor, + // activeCircleColor: AppColors.successColor, + // inactiveCircleColor: AppColors.greyTextColor, + // activeIconColor: AppColors.bgGreenColor, + // inactiveIconColor: AppColors.greyTextColor, + // activeIcon: Utils.buildSvgWithAssets(icon: AppAssets.bell, iconColor: AppColors.whiteColor, width: 15.w, height: 15.h), + // inactiveIcon: Utils.buildSvgWithAssets(icon: AppAssets.bell, iconColor: AppColors.whiteColor, width: 15.w, height: 15.h), + // onChanged: (newValue) async { + // CalenderUtilsNew calender = CalenderUtilsNew.instance; + // bool isEventAddedOrRemoved = false; + // if (newValue == true) { + // DateTime startDate = DateTime.now(); + // DateTime endDate = DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate); + // + // // Show reminder bottom sheet and check if permission was granted + // bool permissionGranted = await BottomSheetUtils().showReminderBottomSheet( + // context, + // endDate, + // widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "", + // "${widget.patientAppointmentHistoryResponseModel.appointmentNo}" ?? "", + // "", + // "", + // title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}", + // description: + // "${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}", + // onSuccess: () { + // setState(() { + // myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); + // }); + // }, + // isMultiAllowed: true, + // onMultiDateSuccess: (int selectedIndex) async { + // isEventAddedOrRemoved = await calender.createOrUpdateEvent( + // title: + // "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}", + // description: + // "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget.patientAppointmentHistoryResponseModel.projectName}", + // scheduleDateTime: DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), + // eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}", + // location: '', + // reminderMinutes: selectedIndex); + // setState(() { + // myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); + // }); + // }, + // ); + // + // // If permission was not granted, revert the switch back to OFF + // if (!permissionGranted) { + // _bellSwitchKey.currentState?.setSwitchValue(false); + // } + // } else { + // isEventAddedOrRemoved = await calender.checkAndRemove( + // id: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}", + // ); + // setState(() { + // myAppointmentsViewModel.setAppointmentReminder(!isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); + // }); + // } + // }, + // ) + // + // // Switch( + // // activeThumbColor: AppColors.successColor, + // // // activeTrackColor: AppColors.successColor.withValues(alpha: .15), + // // value: widget.patientAppointmentHistoryResponseModel.hasReminder!, + // // onChanged: (newValue) async { + // // CalenderUtilsNew calender = CalenderUtilsNew.instance; + // // bool isEventAddedOrRemoved = false; + // // if(newValue == true){ + // // DateTime startDate = DateTime.now(); + // // DateTime endDate = DateUtil.convertStringToDate(widget + // // .patientAppointmentHistoryResponseModel.appointmentDate); + // // BottomSheetUtils().showReminderBottomSheet( + // // context, + // // endDate, + // // widget.patientAppointmentHistoryResponseModel.doctorNameObj??"", + // // "${widget.patientAppointmentHistoryResponseModel.appointmentNo}"??"", + // // "", + // // "", + // // title: "Appointment with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}", + // // description: + // // "${widget.patientAppointmentHistoryResponseModel.doctorNameObj} will be having an appointment on ${widget.patientAppointmentHistoryResponseModel.appointmentDate}", + // // onSuccess: () { + // // setState(() { + // // myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); + // // }); + // // }, + // // isMultiAllowed: true, + // // onMultiDateSuccess: (int selectedIndex) async { + // // isEventAddedOrRemoved = await calender.createOrUpdateEvent( + // // title: + // // "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} on ${DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate)}, Appointment #${widget.patientAppointmentHistoryResponseModel.appointmentNo}", + // // description: + // // "Appointment Reminder with ${widget.patientAppointmentHistoryResponseModel.doctorNameObj} in ${widget.patientAppointmentHistoryResponseModel.projectName}", + // // scheduleDateTime: DateUtil.convertStringToDate(widget + // // .patientAppointmentHistoryResponseModel.appointmentDate), + // // eventId: "${widget.patientAppointmentHistoryResponseModel.appointmentNo}", + // // location: '', + // // reminderMinutes: selectedIndex + // // ); + // // setState(() { + // // myAppointmentsViewModel.setAppointmentReminder(isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); + // // }); + // // }, + // // ); + // // }else { + // // isEventAddedOrRemoved = await calender.checkAndRemove( id:"${widget.patientAppointmentHistoryResponseModel.appointmentNo}", ); + // // setState(() { + // // myAppointmentsViewModel.setAppointmentReminder(!isEventAddedOrRemoved, widget.patientAppointmentHistoryResponseModel); + // // }); + // // } + // // + // // + // // }, + // // ), + // ], + // ).paddingSymmetrical(16.h, 16.h), + // ), SizedBox(height: 16.h), ], ) @@ -471,106 +879,116 @@ class _AppointmentDetailsPageState extends State { ), physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, - shrinkWrap: true, - children: [ - MedicalFileCard( - label: LocaleKeys.labResults.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.lab_result_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() async { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingLabResults.tr(context: context)); - await labViewModel.getLabResultsByAppointmentNo( - appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, - projectID: widget.patientAppointmentHistoryResponseModel.projectID, - clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, - doctorID: widget.patientAppointmentHistoryResponseModel.doctorID, - doctorName: widget.patientAppointmentHistoryResponseModel.doctorNameObj!, - clinicName: widget.patientAppointmentHistoryResponseModel.clinicName!, - projectName: widget.patientAppointmentHistoryResponseModel.projectName!, - appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate!, - onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - }, - onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }, - ); - }), - MedicalFileCard( - label: "${LocaleKeys.radiology.tr(context: context)} ${LocaleKeys.radiologySubtitle.tr(context: context)}", - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.radiology_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() async { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingRadiologyResults.tr(context: context)); - await radiologyViewModel.getPatientRadiologyOrdersByAppointment( - appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, - projectID: widget.patientAppointmentHistoryResponseModel.projectID, - onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - }, - onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }, - ); - }), - MedicalFileCard( - label: LocaleKeys.prescriptions.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.prescription_item_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() async { - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingAppointmentPrescriptions.tr(context: context)); - await prescriptionsViewModel.getPrescriptionDetails( - getPrescriptionRequestModel(), - onSuccess: (val) { - LoaderBottomSheet.hideLoader(); - if (val.data.isNotEmpty) { - PatientPrescriptionsResponseModel patientPrescriptionsResponseModel = PatientPrescriptionsResponseModel( - doctorImageURL: widget.patientAppointmentHistoryResponseModel.doctorImageURL, - doctorName: widget.patientAppointmentHistoryResponseModel.doctorNameObj, - appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate, - clinicDescription: widget.patientAppointmentHistoryResponseModel.clinicName, - decimalDoctorRate: widget.patientAppointmentHistoryResponseModel.decimalDoctorRate, - name: widget.patientAppointmentHistoryResponseModel.projectName, - isHomeMedicineDeliverySupported: val.data.first.isHomeMedicineDeliverySupported ?? false, - projectID: widget.patientAppointmentHistoryResponseModel.projectID, - clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, - doctorID: widget.patientAppointmentHistoryResponseModel.doctorID, - setupID: widget.patientAppointmentHistoryResponseModel.setupID, - isInOutPatient: widget.patientAppointmentHistoryResponseModel.isInOutPatient, - appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, - episodeID: widget.patientAppointmentHistoryResponseModel.episodeID, - dischargeNo: 0, + shrinkWrap: true, + children: [ + MedicalFileCard( + label: LocaleKeys.labResults.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.lab_result_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingLabResults.tr(context: context)); + await labViewModel.getLabResultsByAppointmentNo( + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + doctorID: widget.patientAppointmentHistoryResponseModel.doctorID, + doctorName: widget.patientAppointmentHistoryResponseModel.doctorNameObj!, + clinicName: widget.patientAppointmentHistoryResponseModel.clinicName!, + projectName: widget.patientAppointmentHistoryResponseModel.projectName!, + appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate!, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, ); - Navigator.of(context).push( - CustomPageRoute( - page: PrescriptionDetailPage( - isFromAppointments: true, prescriptionsResponseModel: patientPrescriptionsResponseModel), - ), + }, + ); + }), + MedicalFileCard( + label: "${LocaleKeys.radiology.tr(context: context)} ${LocaleKeys.radiologySubtitle.tr(context: context)}", + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.radiology_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingRadiologyResults.tr(context: context)); + await radiologyViewModel.getPatientRadiologyOrdersByAppointment( + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, ); - } else { + }, + ); + }), + MedicalFileCard( + label: LocaleKeys.prescriptions.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.prescription_item_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingAppointmentPrescriptions.tr(context: context)); + await prescriptionsViewModel.getPrescriptionDetails( + getPrescriptionRequestModel(), + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + if (val.data.isNotEmpty) { + PatientPrescriptionsResponseModel patientPrescriptionsResponseModel = PatientPrescriptionsResponseModel( + doctorImageURL: widget.patientAppointmentHistoryResponseModel.doctorImageURL, + doctorName: widget.patientAppointmentHistoryResponseModel.doctorNameObj, + appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate, + clinicDescription: widget.patientAppointmentHistoryResponseModel.clinicName, + decimalDoctorRate: widget.patientAppointmentHistoryResponseModel.decimalDoctorRate, + name: widget.patientAppointmentHistoryResponseModel.projectName, + isHomeMedicineDeliverySupported: val.data.first.isHomeMedicineDeliverySupported ?? false, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + doctorID: widget.patientAppointmentHistoryResponseModel.doctorID, + setupID: widget.patientAppointmentHistoryResponseModel.setupID, + isInOutPatient: widget.patientAppointmentHistoryResponseModel.isInOutPatient, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, + episodeID: widget.patientAppointmentHistoryResponseModel.episodeID, + dischargeNo: 0, + ); + Navigator.of(context).push( + CustomPageRoute( + page: PrescriptionDetailPage(isFromAppointments: true, prescriptionsResponseModel: patientPrescriptionsResponseModel), + ), + ); + } else { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: LocaleKeys.noPrescriptionsForAppointment.tr(context: context)), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, child: Utils.getErrorWidget(loadingText: LocaleKeys.noPrescriptionsForAppointment.tr(context: context)), @@ -578,40 +996,29 @@ class _AppointmentDetailsPageState extends State { isFullScreen: false, isCloseButtonVisible: true, ); - } - }, - onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: LocaleKeys.noPrescriptionsForAppointment.tr(context: context)), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }, - ); - // Navigator.of(context).push( - // CustomPageRoute( - // page: VaccineListPage(), - // ), - // ); - }), - MedicalFileCard( - label: LocaleKeys.doctorResponses.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.ask_doctor_medical_file_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() { - getIt.get().initAskDoctorViewModel(); - getIt.get().getDoctorResponses(); - Navigator.of(context).push( - CustomPageRoute( - page: DoctorResponsePage(), - ), - ); + }, + ); + // Navigator.of(context).push( + // CustomPageRoute( + // page: VaccineListPage(), + // ), + // ); + }), + MedicalFileCard( + label: LocaleKeys.doctorResponses.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.ask_doctor_medical_file_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() { + getIt.get().initAskDoctorViewModel(); + getIt.get().getDoctorResponses(); + Navigator.of(context).push( + CustomPageRoute( + page: DoctorResponsePage(), + ), + ); }), myAppointmentsVM.isAppointmentRatedResponseLoading ? MedicalFileCard( @@ -882,8 +1289,7 @@ class _AppointmentDetailsPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (widget.patientAppointmentHistoryResponseModel.nextAction == 15 || - widget.patientAppointmentHistoryResponseModel.nextAction == 20) + if (widget.patientAppointmentHistoryResponseModel.nextAction == 15 || widget.patientAppointmentHistoryResponseModel.nextAction == 20) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -891,10 +1297,7 @@ class _AppointmentDetailsPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LocaleKeys.amountBeforeTax.tr(context: context).toText18(isBold: true), - Utils.getPaymentAmountWithSymbol( - widget.patientAppointmentHistoryResponseModel.patientShare!.toString().toText16(isBold: true), - AppColors.blackColor, - 13, + Utils.getPaymentAmountWithSymbol(widget.patientAppointmentHistoryResponseModel.patientShare!.toString().toText16(isBold: true), AppColors.blackColor, 13, isSaudiCurrency: true), ], ), @@ -902,9 +1305,7 @@ class _AppointmentDetailsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded( - child: LocaleKeys.upcomingPaymentNow - .tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor)), + Expanded(child: LocaleKeys.upcomingPaymentNow.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor)), "VAT 15%(${widget.patientAppointmentHistoryResponseModel.patientTaxAmount})".toText14(isBold: true, color: AppColors.greyTextColor, letterSpacing: -0.64), ], ), @@ -919,10 +1320,7 @@ class _AppointmentDetailsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Utils.getPaymentAmountWithSymbol( - widget.patientAppointmentHistoryResponseModel.patientShareWithTax!.toString().toText24(isBold: true), - AppColors.blackColor, - 17, + Utils.getPaymentAmountWithSymbol(widget.patientAppointmentHistoryResponseModel.patientShareWithTax!.toString().toText24(isBold: true), AppColors.blackColor, 17, isSaudiCurrency: true), ], ), @@ -934,6 +1332,7 @@ class _AppointmentDetailsPageState extends State { ? (!(widget.patientAppointmentHistoryResponseModel.isActiveDoctor ?? true) || widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! || widget.patientAppointmentHistoryResponseModel.isExecludeDoctor! || + widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed == false || !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID)) ? SizedBox.shrink() : CustomButton( @@ -945,11 +1344,11 @@ class _AppointmentDetailsPageState extends State { borderColor: AppColors.primaryRedColor, textColor: Colors.white, fontSize: 16.f, - fontWeight: FontWeight.w600, + fontWeight: FontWeight.w600, borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - height: 50.h, - icon: AppAssets.add_icon, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 50.h, + icon: AppAssets.add_icon, iconColor: Colors.white, iconSize: 18.h, ).paddingSymmetrical(16.h, 24.h) @@ -959,8 +1358,7 @@ class _AppointmentDetailsPageState extends State { handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction); }, backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), - borderColor: - AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), textColor: widget.patientAppointmentHistoryResponseModel.nextAction == 15 ? AppColors.textColor : Colors.white, fontSize: 16.f, fontWeight: FontWeight.w600, @@ -1121,19 +1519,93 @@ class _AppointmentDetailsPageState extends State { myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.getPatientAppointments(true, false); Navigator.of(context).pop(); - }, title: "", height: ResponsiveExtension.screenHeight * 0.3, isAutoDismiss: true, isCloseButtonVisible: true, isDismissible: false, isFullScreen: false, - isSuccessDialog: true); + }, title: "", height: ResponsiveExtension.screenHeight * 0.3, isAutoDismiss: true, isCloseButtonVisible: true, isDismissible: false, isFullScreen: false, isSuccessDialog: true); }); // LoaderBottomSheet.hideLoader(); case 15: - getIt.get().showCommonBottomSheetWithoutH( - message: LocaleKeys.upcomingPaymentPending.tr(context: context), - label: LocaleKeys.notice.tr(), - onOkPressed: () {}, - okLabel: "confirm", - cancelLabel: LocaleKeys.acknowledged.tr(context: context), - isConfirmButton: true, - ); + // Ensure timer is running before showing modal + if (_countdownTimer == null || !_countdownTimer!.isActive) { + _startCountdownTimer(); + } + + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.notice.tr(), + child: StatefulBuilder( + builder: (context, setModalState) { + // Store the modal setState callback + _modalSetState = setModalState; + + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Lottie.asset(AppAnimations.warningAnimation, repeat: false, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), + SizedBox( + height: 12, + ), + LocaleKeys.upcomingPaymentPending.tr(context: context).toText14(color: AppColors.textColor, isCenter: true), + SizedBox(height: 24.h), + // // Countdown Timer - DD : HH : MM : SS format with labels - Always LTR + // Directionality( + // textDirection: ui.TextDirection.ltr, + // child: Row( + // mainAxisAlignment: MainAxisAlignment.center, + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // // Days + // _buildTimeUnit( + // _timeRemaining != null ? _timeRemaining!.inDays.toString().padLeft(2, '0') : '00', + // LocaleKeys.days.tr(context: context), + // ), + // _buildTimeSeparator(), + // // Hours + // _buildTimeUnit( + // _timeRemaining != null ? _timeRemaining!.inHours.remainder(24).toString().padLeft(2, '0') : '00', + // LocaleKeys.hours.tr(context: context), + // ), + // _buildTimeSeparator(), + // // Minutes + // _buildTimeUnit( + // _timeRemaining != null ? _timeRemaining!.inMinutes.remainder(60).toString().padLeft(2, '0') : '00', + // LocaleKeys.minutes.tr(context: context), + // ), + // _buildTimeSeparator(), + // // Seconds + // _buildTimeUnit( + // _timeRemaining != null ? _timeRemaining!.inSeconds.remainder(60).toString().padLeft(2, '0') : '00', + // LocaleKeys.seconds.tr(context: context), + // ), + // ], + // ), + // ), + // SizedBox(height: 24.h), + // Green Acknowledge button with checkmark icon + CustomButton( + text: LocaleKeys.acknowledged.tr(context: context), + onPressed: () { + _modalSetState = null; // Clear callback when closing + Navigator.of(context).pop(); + }, + backgroundColor: AppColors.successColor, + borderColor: AppColors.successColor, + textColor: Colors.white, + fontSize: 16.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + height: 50.h, + icon: AppAssets.checkmark_icon, + iconColor: Colors.white, + ), + ], + ); + }, + ), + callBackFunc: () { + _modalSetState = null; // Clear callback when closing + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); case 20: myAppointmentsViewModel.setIsPatientAppointmentShareLoading(true); Navigator.of(context).push( diff --git a/lib/presentation/appointments/my_doctors_page.dart b/lib/presentation/appointments/my_doctors_page.dart index 93733770..7cf8155a 100644 --- a/lib/presentation/appointments/my_doctors_page.dart +++ b/lib/presentation/appointments/my_doctors_page.dart @@ -222,7 +222,7 @@ class _MyDoctorsPageState extends State { richText: Row( children: [ "${group.length} ".toText10(isEnglishOnly: true), - "${LocaleKeys.doctors.tr(context: context)} ".toText10() + "${LocaleKeys.doctors2.tr(context: context)} ".toText10() ], ), // labelText: "${group.length} ${'doctors'}", diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index db08c923..8b178c47 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -17,7 +18,6 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/appointment_details_page.dart'; -import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor_request_type_select.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/eye_measurement_details_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -27,8 +27,10 @@ import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'dart:ui' as ui; +import 'package:hmg_patient_app_new/presentation/appointments/appointment_payment_page.dart'; +import 'package:lottie/lottie.dart'; -class AppointmentCard extends StatelessWidget { +class AppointmentCard extends StatefulWidget { final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; final MyAppointmentsViewModel myAppointmentsViewModel; final bool isLoading; @@ -56,6 +58,97 @@ class AppointmentCard extends StatelessWidget { this.contactUsViewModel, this.isForRate = false}); + @override + State createState() => _AppointmentCardState(); +} + +class _AppointmentCardState extends State { + Timer? _countdownTimer; + Duration? _timeRemaining; + Function(void Function())? _modalSetState; + + @override + void initState() { + super.initState(); + // Start countdown timer if appointment requires payment + if (widget.patientAppointmentHistoryResponseModel.nextAction == 15) { + _startCountdownTimer(); + } + } + + @override + void dispose() { + _countdownTimer?.cancel(); + super.dispose(); + } + + void _startCountdownTimer() { + final appointmentDate = DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate!); + final expiryDate = appointmentDate.subtract(const Duration(hours: 4)); + + setState(() { + _timeRemaining = expiryDate.difference(DateTime.now()); + }); + + _countdownTimer?.cancel(); + _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { + final newTimeRemaining = expiryDate.difference(DateTime.now()); + + if (newTimeRemaining.isNegative) { + timer.cancel(); + setState(() { + _timeRemaining = Duration.zero; + }); + _modalSetState?.call(() { + _timeRemaining = Duration.zero; + }); + } else { + setState(() { + _timeRemaining = newTimeRemaining; + }); + _modalSetState?.call(() { + _timeRemaining = newTimeRemaining; + }); + } + }); + } + + // Helper method to build each time unit (number + label) + Widget _buildTimeUnit(String value, String label) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + alignment: Alignment.center, + child: value.toText32( + isBold: true, + color: AppColors.blackColor, + isEnglishOnly: true, + ), + ), + SizedBox(height: 4.h), + Container( + alignment: Alignment.center, + child: label.toText12( + color: AppColors.greyTextColor, + ), + ), + ], + ); + } + + // Helper method to build time separator (:) + Widget _buildTimeSeparator() { + return Padding( + padding: EdgeInsets.only(bottom: 16.h, left: 6.w, right: 6.w), + child: ':'.toText32( + isBold: true, + color: AppColors.blackColor, + ), + ); + } + @override Widget build(BuildContext context) { final appState = getIt.get(); @@ -66,11 +159,11 @@ class AppointmentCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - isForRate ? SizedBox() : _buildHeader(context, appState), + widget.isForRate ? SizedBox() : _buildHeader(context, appState), SizedBox(height: 16.h), _buildDoctorRow(context), SizedBox(height: 16.h), - isForRate ? SizedBox() : _buildActionArea(context, appState), + widget.isForRate ? SizedBox() : _buildActionArea(context, appState), ], ), ), @@ -87,7 +180,7 @@ class AppointmentCard extends StatelessWidget { } Widget _buildChips(BuildContext context, AppState appState) { - final isLiveCare = !isLoading && patientAppointmentHistoryResponseModel.isLiveCareAppointment!; + final isLiveCare = !widget.isLoading && widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!; return Wrap( alignment: WrapAlignment.start, @@ -96,25 +189,25 @@ class AppointmentCard extends StatelessWidget { runSpacing: 6.h, children: [ AppCustomChipWidget( - icon: isLoading ? AppAssets.walkin_appointment_icon : (isLiveCare ? AppAssets.small_livecare_icon : AppAssets.walkin_appointment_icon), - iconColor: isLoading ? AppColors.textColor : (isLiveCare ? Colors.white : AppColors.textColor), - labelText: isLoading ? LocaleKeys.walkin.tr(context: context) : (isLiveCare ? LocaleKeys.livecare.tr(context: context) : LocaleKeys.walkin.tr(context: context)), - backgroundColor: isLoading ? AppColors.greyColor : (isLiveCare ? AppColors.successColor : AppColors.greyColor), - textColor: isLoading ? AppColors.textColor : (isLiveCare ? Colors.white : AppColors.textColor), - ).toShimmer2(isShow: isLoading), + icon: widget.isLoading ? AppAssets.walkin_appointment_icon : (isLiveCare ? AppAssets.small_livecare_icon : AppAssets.walkin_appointment_icon), + iconColor: widget.isLoading ? AppColors.textColor : (isLiveCare ? Colors.white : AppColors.textColor), + labelText: widget.isLoading ? LocaleKeys.walkin.tr(context: context) : (isLiveCare ? LocaleKeys.livecare.tr(context: context) : LocaleKeys.walkin.tr(context: context)), + backgroundColor: widget.isLoading ? AppColors.greyColor : (isLiveCare ? AppColors.successColor : AppColors.greyColor), + textColor: widget.isLoading ? AppColors.textColor : (isLiveCare ? Colors.white : AppColors.textColor), + ).toShimmer2(isShow: widget.isLoading), AppCustomChipWidget( labelText: - isLoading ? 'OutPatient' : (appState.isArabic() ? patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! : patientAppointmentHistoryResponseModel.isInOutPatientDescription!), + widget.isLoading ? 'OutPatient' : (appState.isArabic() ? widget.patientAppointmentHistoryResponseModel.isInOutPatientDescriptionN! : widget.patientAppointmentHistoryResponseModel.isInOutPatientDescription!), backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.1), textColor: AppColors.warningColorYellow, - ).toShimmer2(isShow: isLoading), + ).toShimmer2(isShow: widget.isLoading), AppCustomChipWidget( - labelText: isLoading ? 'Booked' : AppointmentType.getAppointmentStatusType(patientAppointmentHistoryResponseModel.patientStatusType!), + labelText: widget.isLoading ? 'Booked' : AppointmentType.getAppointmentStatusType(widget.patientAppointmentHistoryResponseModel.patientStatusType!), backgroundColor: AppColors.successColor.withValues(alpha: 0.1), textColor: AppColors.successColor, - ).toShimmer2(isShow: isLoading), + ).toShimmer2(isShow: widget.isLoading), ], - ).toShimmer2(isShow: isLoading); + ).toShimmer2(isShow: widget.isLoading); } Widget _buildDoctorRow(BuildContext context) { @@ -125,11 +218,11 @@ class AppointmentCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.network( - isLoading ? 'https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png' : patientAppointmentHistoryResponseModel.doctorImageURL!, + widget.isLoading ? 'https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png' : widget.patientAppointmentHistoryResponseModel.doctorImageURL!, width: 63.h, height: 63.h, fit: BoxFit.cover, - ).circle(100.r).toShimmer2(isShow: isLoading), + ).circle(100.r).toShimmer2(isShow: widget.isLoading), Transform.translate( offset: Offset(0.0, -20.h), child: Container( @@ -149,11 +242,11 @@ class AppointmentCard extends StatelessWidget { Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h, iconColor: AppColors.ratingColorYellow), SizedBox(height: 2.h), (isFoldable || isTablet) - ? "${patientAppointmentHistoryResponseModel.decimalDoctorRate}".toText9(isBold: true, color: AppColors.textColor, isEnglishOnly: true) - : "${patientAppointmentHistoryResponseModel.decimalDoctorRate ?? "0.0"}".toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true), + ? "${widget.patientAppointmentHistoryResponseModel.decimalDoctorRate}".toText9(isBold: true, color: AppColors.textColor, isEnglishOnly: true) + : "${widget.patientAppointmentHistoryResponseModel.decimalDoctorRate ?? "0.0"}".toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true), ], ), - ).circle(100).toShimmer2(isShow: isLoading), + ).circle(100).toShimmer2(isShow: widget.isLoading), ), ], ), @@ -164,20 +257,20 @@ class AppointmentCard extends StatelessWidget { children: [ Row( children: [ - (isLoading ? 'Dr' : "${patientAppointmentHistoryResponseModel.doctorTitle}").toText16(isBold: true, maxlines: 1), - (isLoading ? 'John Doe' : " ${patientAppointmentHistoryResponseModel.doctorNameObj!.truncate(20)}") - .toText16(isBold: true, maxlines: 1, isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? "John Doe")), + (widget.isLoading ? 'Dr' : "${widget.patientAppointmentHistoryResponseModel.doctorTitle}").toText16(isBold: true, maxlines: 1), + (widget.isLoading ? 'John Doe' : " ${widget.patientAppointmentHistoryResponseModel.doctorNameObj!.truncate(20)}") + .toText16(isBold: true, maxlines: 1, isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "John Doe")), SizedBox(width: 12.w), - (patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null && patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty) + (widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null && widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty) ? Image.network( - patientAppointmentHistoryResponseModel.doctorNationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png", + widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png", width: 20.h, height: 15.h, fit: BoxFit.cover, ) : SizedBox.shrink(), ], - ).toShimmer2(isShow: isLoading), + ).toShimmer2(isShow: widget.isLoading), SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, @@ -185,29 +278,29 @@ class AppointmentCard extends StatelessWidget { runSpacing: 4.h, children: [ AppCustomChipWidget( - labelText: isLoading + labelText: widget.isLoading ? 'Cardiology' - : (patientAppointmentHistoryResponseModel.clinicName!.length > 20 - ? '${patientAppointmentHistoryResponseModel.clinicName!.substring(0, 20)}...' - : patientAppointmentHistoryResponseModel.clinicName!), - ).toShimmer2(isShow: isLoading), + : (widget.patientAppointmentHistoryResponseModel.clinicName!.length > 20 + ? '${widget.patientAppointmentHistoryResponseModel.clinicName!.substring(0, 20)}...' + : widget.patientAppointmentHistoryResponseModel.clinicName!), + ).toShimmer2(isShow: widget.isLoading), AppCustomChipWidget( - labelText: isLoading + labelText: widget.isLoading ? 'Olaya' - : (patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital").length > 15 - ? '${(patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital").substring(0, 15)}...' - : patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital") - .toShimmer2(isShow: isLoading), + : (widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital").length > 15 + ? '${(widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital").substring(0, 15)}...' + : widget.patientAppointmentHistoryResponseModel.projectName ?? "Habib Hospital") + .toShimmer2(isShow: widget.isLoading), Directionality( textDirection: ui.TextDirection.ltr, child: AppCustomChipWidget( labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w), icon: AppAssets.appointment_calendar_icon, - richText: isLoading - ? 'Cardiology'.toText10().toShimmer2(isShow: isLoading) - : "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)} ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}" + 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)}" .toText10(isEnglishOnly: true, isBold: true), - ).toShimmer2(isShow: isLoading), + ).toShimmer2(isShow: widget.isLoading), ), // AppCustomChipWidget( @@ -240,10 +333,10 @@ class AppointmentCard extends StatelessWidget { } Widget _buildActionArea(BuildContext context, AppState appState) { - if ((((patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || - (patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || - !Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID ?? 0))) && - AppointmentType.isArrived(patientAppointmentHistoryResponseModel)) { + if ((((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || + (widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || + !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID ?? 0))) && + AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) { // if (((patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) && // DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15)) { // return Row( @@ -325,25 +418,25 @@ class AppointmentCard extends StatelessWidget { // ); // } else { return CustomButton( - text: isFromMedicalReport ? LocaleKeys.selectAppointment.tr(context: context) : LocaleKeys.viewDetails.tr(context: context), + text: widget.isFromMedicalReport ? LocaleKeys.selectAppointment.tr(context: context) : LocaleKeys.viewDetails.tr(context: context), onPressed: () { - if (isFromMedicalReport) { - if (isForFeedback) { - contactUsViewModel!.setPatientFeedbackSelectedAppointment(patientAppointmentHistoryResponseModel); + if (widget.isFromMedicalReport) { + if (widget.isForFeedback) { + widget.contactUsViewModel!.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel); } else { - medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel); + widget.medicalFileViewModel!.setSelectedMedicalReportAppointment(widget.patientAppointmentHistoryResponseModel); } Navigator.pop(context, false); } else { Navigator.of(context) .push( CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), ), ) .then((_) { - myAppointmentsViewModel.initAppointmentsViewModel(); - myAppointmentsViewModel.getPatientAppointments(true, false); + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); }); } }, @@ -356,23 +449,23 @@ class AppointmentCard extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: 10.w), // height: isTablet || isFoldable ? 46.h : 40.h, height: 40.h, - icon: isFromMedicalReport ? AppAssets.checkmark_icon : null, + icon: widget.isFromMedicalReport ? AppAssets.checkmark_icon : null, iconColor: AppColors.primaryRedColor, iconSize: 16.h, ); // } } else { - if (isFromMedicalReport) { - if (isForEyeMeasurements) { + if (widget.isFromMedicalReport) { + if (widget.isForEyeMeasurements) { return SizedBox.shrink(); } else { return CustomButton( text: LocaleKeys.selectAppointment.tr(context: context), onPressed: () { - if (isForFeedback) { - contactUsViewModel!.setPatientFeedbackSelectedAppointment(patientAppointmentHistoryResponseModel); + if (widget.isForFeedback) { + widget.contactUsViewModel!.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel); } else { - medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel); + widget.medicalFileViewModel!.setSelectedMedicalReportAppointment(widget.patientAppointmentHistoryResponseModel); } Navigator.pop(context, false); }, @@ -391,71 +484,75 @@ class AppointmentCard extends StatelessWidget { ); } } - return (patientAppointmentHistoryResponseModel.isActiveDoctor ?? true) - ? Row( - children: [ - Expanded( - flex: 6, - child: (AppointmentType.isArrived(patientAppointmentHistoryResponseModel) - ? _getArrivedButton(context) - : CustomButton( - text: AppointmentType.getNextActionText(patientAppointmentHistoryResponseModel.nextAction), - onPressed: () => _goToDetails(context), - backgroundColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15), - borderColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), - textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), - fontSize: (isFoldable || isTablet) ? 12.f : 14.f, - fontWeight: FontWeight.w600, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - height: 40.h, - icon: AppointmentType.getNextActionIcon(patientAppointmentHistoryResponseModel.nextAction), - iconColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), - iconSize: 15.h, - )) - .toShimmer2(isShow: isLoading), - ), - SizedBox(width: 8.h), - Expanded( - flex: 1, - child: Container( - height: (isFoldable || isTablet) ? 50.h : 40.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.transparent, - borderRadius: 10.h, - side: BorderSide( - color: AppColors.textColor, - width: 1.2, - ), - ), - child: Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, - iconColor: AppColors.textColor, - width: 24.w, - height: 24.h, - fit: BoxFit.contain, - ), - ), - ).toShimmer2(isShow: isLoading).onPress(() { - _goToDetails(context); - }), - ), - ], - ) + return (widget.patientAppointmentHistoryResponseModel.isActiveDoctor ?? true) + ? (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) && + widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed == false) + ? // Show only View Details button without arrow when rebooking not allowed + _getArrivedButton(context) + : Row( + children: [ + Expanded( + flex: 6, + child: (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? _getArrivedButton(context) + : CustomButton( + text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), + onPressed: () => handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction, context), + backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), + textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), + iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), + iconSize: 15.h, + )) + .toShimmer2(isShow: widget.isLoading), + ), + SizedBox(width: 8.h), + Expanded( + flex: 1, + child: Container( + height: (isFoldable || isTablet) ? 50.h : 40.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.transparent, + borderRadius: 10.h, + side: BorderSide( + color: AppColors.textColor, + width: 1.2, + ), + ), + child: Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.textColor, + width: 24.w, + height: 24.h, + fit: BoxFit.contain, + ), + ), + ).toShimmer2(isShow: widget.isLoading).onPress(() { + _goToDetails(context); + }), + ), + ], + ) : CustomButton( text: LocaleKeys.viewDetails.tr(context: context), onPressed: () { Navigator.of(context) .push( CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), ), ) .then((_) { - myAppointmentsViewModel.initAppointmentsViewModel(); - myAppointmentsViewModel.getPatientAppointments(true, false); + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); }); }, backgroundColor: AppColors.secondaryLightRedColor, @@ -467,7 +564,7 @@ class AppointmentCard extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: 10.w), // height: isTablet || isFoldable ? 46.h : 40.h, height: 40.h, - icon: isFromMedicalReport ? AppAssets.checkmark_icon : null, + icon: widget.isFromMedicalReport ? AppAssets.checkmark_icon : null, iconColor: AppColors.primaryRedColor, iconSize: 16.h, ); @@ -475,54 +572,22 @@ class AppointmentCard extends StatelessWidget { } Widget _getArrivedButton(BuildContext context) { - // final isRecent = DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15; - // - // if (isRecent) { - // return CustomButton( - // text: LocaleKeys.askDoctor.tr(context: context), - // onPressed: () async { - // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); - // await myAppointmentsViewModel.isDoctorAvailable( - // projectID: patientAppointmentHistoryResponseModel.projectID, - // doctorId: patientAppointmentHistoryResponseModel.doctorID, - // clinicId: patientAppointmentHistoryResponseModel.clinicID, - // onSuccess: (value) async { - // if (value) { - // await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) { - // LoaderBottomSheet.hideLoader(); - // showCommonBottomSheetWithoutHeight( - // context, - // title: LocaleKeys.askDoctor.tr(context: context), - // child: AskDoctorRequestTypeSelect( - // askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList, - // myAppointmentsViewModel: myAppointmentsViewModel, - // patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, - // ), - // callBackFunc: () {}, - // isFullScreen: false, - // isCloseButtonVisible: true, - // ); - // }); - // } else { - // print("Doctor is not available"); - // } - // }); - // }, - // backgroundColor: AppColors.secondaryLightRedColor, - // borderColor: AppColors.secondaryLightRedColor, - // textColor: AppColors.primaryRedColor, - // fontSize: (isFoldable || isTablet) ? 12.f : 14.f, - // fontWeight: FontWeight.w600, - // borderRadius: 12.r, - // padding: EdgeInsets.symmetric(horizontal: 10.w), - // // height: isTablet || isFoldable ? 46.h : 40.h, - // height: 40.h, - // icon: AppAssets.ask_doctor_icon, - // iconColor: AppColors.primaryRedColor, - // iconSize: 16.h, - // ); - // } + if (widget.patientAppointmentHistoryResponseModel.isClinicReBookingAllowed == false) { + return CustomButton( + text: LocaleKeys.viewDetails.tr(context: context), + onPressed: () => _goToDetails(context), + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: (isFoldable || isTablet) ? 12.f : 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: 40.h, + ); + } + // Show Rebook button return CustomButton( borderSide: BorderSide( color: AppColors.textColor, @@ -546,21 +611,21 @@ class AppointmentCard extends StatelessWidget { } void _goToDetails(BuildContext context) { - if (isFromMedicalReport) return; - if (isForEyeMeasurements) { + if (widget.isFromMedicalReport) return; + if (widget.isForEyeMeasurements) { Navigator.of(context).push( CustomPageRoute( - page: EyeMeasurementDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), + page: EyeMeasurementDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), ), ); } else { - if (!AppointmentType.isArrived(patientAppointmentHistoryResponseModel)) { - bookAppointmentsViewModel.getAppointmentNearestGate(projectID: patientAppointmentHistoryResponseModel.projectID, clinicID: patientAppointmentHistoryResponseModel.clinicID); + if (!AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) { + widget.bookAppointmentsViewModel.getAppointmentNearestGate(projectID: widget.patientAppointmentHistoryResponseModel.projectID, clinicID: widget.patientAppointmentHistoryResponseModel.clinicID); } Navigator.of(context) .push( CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), ), ) .then((_) { @@ -572,22 +637,22 @@ class AppointmentCard extends StatelessWidget { void openDoctorScheduleCalendar(BuildContext context) async { final doctor = DoctorsListResponseModel( - clinicID: patientAppointmentHistoryResponseModel.clinicID, - projectID: patientAppointmentHistoryResponseModel.projectID, - doctorID: patientAppointmentHistoryResponseModel.doctorID, - doctorImageURL: patientAppointmentHistoryResponseModel.doctorImageURL, - doctorTitle: patientAppointmentHistoryResponseModel.doctorTitle, - name: patientAppointmentHistoryResponseModel.doctorNameObj, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + doctorID: widget.patientAppointmentHistoryResponseModel.doctorID, + doctorImageURL: widget.patientAppointmentHistoryResponseModel.doctorImageURL, + doctorTitle: widget.patientAppointmentHistoryResponseModel.doctorTitle, + name: widget.patientAppointmentHistoryResponseModel.doctorNameObj, nationalityFlagURL: '', speciality: [], - clinicName: patientAppointmentHistoryResponseModel.clinicName, - projectName: patientAppointmentHistoryResponseModel.projectName, + clinicName: widget.patientAppointmentHistoryResponseModel.clinicName, + projectName: widget.patientAppointmentHistoryResponseModel.projectName, ); - bookAppointmentsViewModel.setSelectedDoctor(doctor); + widget.bookAppointmentsViewModel.setSelectedDoctor(doctor); LoaderBottomSheet.showLoader(); - await bookAppointmentsViewModel.getDoctorFreeSlots( + await widget.bookAppointmentsViewModel.getDoctorFreeSlots( isBookingForLiveCare: false, onSuccess: (respData) async { LoaderBottomSheet.hideLoader(); @@ -612,4 +677,127 @@ class AppointmentCard extends StatelessWidget { }, ); } + + Future handleAppointmentNextAction(nextAction, BuildContext context) async { + switch (nextAction) { + case 0: + // No action needed + _goToDetails(context); + break; + case 10: + // Confirm appointment - go to details + _goToDetails(context); + break; + case 15: + // Pending payment - show waiting modal + // Ensure timer is running before showing modal + if (_countdownTimer == null || !_countdownTimer!.isActive) { + _startCountdownTimer(); + } + + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.notice.tr(), + child: StatefulBuilder( + builder: (context, setModalState) { + // Store the modal setState callback + _modalSetState = setModalState; + + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Lottie.asset(AppAnimations.warningAnimation, + repeat: false, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), + SizedBox(height: 12,), + LocaleKeys.upcomingPaymentPending.tr(context: context).toText14( + color: AppColors.textColor, + isCenter: true, + ), + SizedBox(height: 24.h), + + + // Countdown Timer - DD : HH : MM : SS format with labels + Directionality( + textDirection: ui.TextDirection.ltr, + child:Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Days + _buildTimeUnit( + _timeRemaining != null ? _timeRemaining!.inDays.toString().padLeft(2, '0') : '00', + LocaleKeys.days.tr(context: context), + ), + _buildTimeSeparator(), + // Hours + _buildTimeUnit( + _timeRemaining != null ? _timeRemaining!.inHours.remainder(24).toString().padLeft(2, '0') : '00', + LocaleKeys.hours.tr(context: context), + ), + _buildTimeSeparator(), + // Minutes + _buildTimeUnit( + _timeRemaining != null ? _timeRemaining!.inMinutes.remainder(60).toString().padLeft(2, '0') : '00', + LocaleKeys.minutes.tr(context: context), + ), + _buildTimeSeparator(), + // Seconds + _buildTimeUnit( + _timeRemaining != null ? _timeRemaining!.inSeconds.remainder(60).toString().padLeft(2, '0') : '00', + LocaleKeys.seconds.tr(context: context), + ), + ], + )), + SizedBox(height: 24.h), + // Green Acknowledge button with checkmark icon + CustomButton( + text: LocaleKeys.acknowledged.tr(context: context), + onPressed: () { + _modalSetState = null; // Clear callback when closing + Navigator.of(context).pop(); + }, + backgroundColor: AppColors.successColor, + borderColor: AppColors.successColor, + textColor: Colors.white, + fontSize: 16.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + height: 50.h, + icon: AppAssets.checkmark_icon, + iconColor: Colors.white, + ), + ], + ); + }, + ), + callBackFunc: () { + _modalSetState = null; // Clear callback when closing + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); + break; + case 20: + // Pay now - navigate to payment page + widget.myAppointmentsViewModel.setIsPatientAppointmentShareLoading(true); + Navigator.of(context).push( + CustomPageRoute( + page: AppointmentPaymentPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + ), + ); + break; + case 50: + // Confirm livecare - go to details + _goToDetails(context); + break; + case 90: + // Check-in - go to details + _goToDetails(context); + break; + default: + // Default - go to details + _goToDetails(context); + } + } } + diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index 4b5df07b..05358243 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -24,7 +24,13 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; class AppointmentDoctorCard extends StatelessWidget { const AppointmentDoctorCard( - {super.key, required this.patientAppointmentHistoryResponseModel, required this.onRescheduleTap, required this.onCancelTap, required this.onAskDoctorTap, this.renderWidgetForERDisplay = false}); + {super.key, + required this.patientAppointmentHistoryResponseModel, + required this.onRescheduleTap, + required this.onCancelTap, + required this.onAskDoctorTap, + this.renderWidgetForERDisplay = false, + this.timerWidget}); final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; final VoidCallback onRescheduleTap; @@ -32,6 +38,8 @@ class AppointmentDoctorCard extends StatelessWidget { final VoidCallback onAskDoctorTap; final bool renderWidgetForERDisplay; + final Widget? timerWidget; + @override Widget build(BuildContext context) { return Container( @@ -142,43 +150,44 @@ class AppointmentDoctorCard extends StatelessWidget { ], ), ), - patientAppointmentHistoryResponseModel.isLiveCareAppointment! + + patientAppointmentHistoryResponseModel.isLiveCareAppointment! || patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! ==false || patientAppointmentHistoryResponseModel.isActiveDoctor! == false ? 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; + 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().setSelectedDoctor(selectedDoctor); - LoaderBottomSheet.showLoader(); - await getIt.get().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, - ); - }); - }) + getIt.get().setSelectedDoctor(selectedDoctor); + LoaderBottomSheet.showLoader(); + await getIt.get().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, + ); + }); + }) ], ), SizedBox(height: 8.h), @@ -188,6 +197,8 @@ class AppointmentDoctorCard extends StatelessWidget { AppointmentType.isArrived(patientAppointmentHistoryResponseModel), ), ), + if (timerWidget != null) + timerWidget ?? SizedBox() ], ), ), @@ -215,26 +226,26 @@ class AppointmentDoctorCard extends StatelessWidget { iconColor: AppColors.primaryRedColor, ) : - // !patientAppointmentHistoryResponseModel.isLiveCareAppointment! - // ? CustomButton( - // text: LocaleKeys.rebookSameDoctor.tr(), - // onPressed: () { - // onRescheduleTap(); - // }, - // backgroundColor: AppColors.greyColor, - // borderColor: AppColors.greyColor, - // textColor: AppColors.blackColor, - // fontSize: 12.f, - // isBold: true, - // borderRadius: 12.r, - // padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), - // height: 40.h, - // icon: AppAssets.rebook_appointment_icon, - // iconColor: AppColors.blackColor, - // iconSize: 14.h, - // ) - // : - SizedBox.shrink(); + // !patientAppointmentHistoryResponseModel.isLiveCareAppointment! + // ? CustomButton( + // text: LocaleKeys.rebookSameDoctor.tr(), + // onPressed: () { + // onRescheduleTap(); + // }, + // backgroundColor: AppColors.greyColor, + // borderColor: AppColors.greyColor, + // textColor: AppColors.blackColor, + // fontSize: 12.f, + // isBold: true, + // borderRadius: 12.r, + // padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 0), + // height: 40.h, + // icon: AppAssets.rebook_appointment_icon, + // iconColor: AppColors.blackColor, + // iconSize: 14.h, + // ) + // : + SizedBox.shrink(); } else { return patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? CustomButton( diff --git a/lib/presentation/authentication/login.dart b/lib/presentation/authentication/login.dart index d559ebbc..05164dec 100644 --- a/lib/presentation/authentication/login.dart +++ b/lib/presentation/authentication/login.dart @@ -70,12 +70,13 @@ class LoginScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Utils.showLottie(context: context, assetPath: AppAnimations.login, width: 200.h, height: 200.h, repeat: true, fit: BoxFit.cover), - SizedBox(height: 130.h), // Adjusted to sizer unit - LocaleKeys.welcomeToDrSulaiman.tr(context: context).toText32(isBold: true, color: AppColors.textColor), + // SizedBox(height: 130.h), + SizedBox(height: MediaQuery.of(context).size.height * 0.12), // based on the screen height Adjusted to sizer unit + LocaleKeys.welcomeToDrSulaiman.tr(context: context).toText32(isBold: true, color: AppColors.textColor ), SizedBox(height: 32.h), Localizations.override(context: context, locale: Locale('en', 'US'), child: Container()), // Force English locale for this widget TextInputWidget( - labelText: "${LocaleKeys.nationalId.tr(context: context)} / ${LocaleKeys.fileNo.tr(context: context)}", + labelText: "${LocaleKeys.nationalIdFileNumber.tr(context: context)}", hintText: "xxxxxxxxx", controller: authVm.nationalIdController, focusNode: _nationalIdFocusNode, diff --git a/lib/presentation/authentication/quick_login.dart b/lib/presentation/authentication/quick_login.dart index 319402b0..133e38ef 100644 --- a/lib/presentation/authentication/quick_login.dart +++ b/lib/presentation/authentication/quick_login.dart @@ -68,7 +68,7 @@ class QuickLoginState extends State { // Image.asset(AppAssets.lockIcon, height: 100), Utils.buildSvgWithAssets(icon: AppAssets.biometricLockIcon, iconColor: AppColors.textColor, width: 100.h, height: 100.h), SizedBox(height: 10.h), - LocaleKeys.enableQuickLogin.tr(context: context).toText26(isBold: true), + LocaleKeys.quickLogin.tr(context: context).toText26(isBold: true), // Text( // ' TranslationBase.of(context).enableQuickLogin', // style: context.dynamicTextStyle( @@ -77,8 +77,8 @@ class QuickLoginState extends State { // color: Colors.black, // ), // ), - SizedBox(height: 5.h), - LocaleKeys.enableQuickLogin.tr(context: context).toText16(color: AppColors.quickLoginColor), + // SizedBox(height: 5.h), + // LocaleKeys.enableQuickLogin.tr(context: context).toText16(color: AppColors.quickLoginColor), // Description // Text( // 'TranslationBase.of(context).enableMsg', @@ -95,7 +95,7 @@ class QuickLoginState extends State { children: [ Expanded( child: CustomButton( - text: LocaleKeys.enableQuickLogin.tr(context: context), + text: LocaleKeys.enableQuickLoginBtn.tr(context: context), onPressed: () { widget.onPressed(); }, diff --git a/lib/presentation/authentication/register_step2.dart b/lib/presentation/authentication/register_step2.dart index f01dd036..4a628fd3 100644 --- a/lib/presentation/authentication/register_step2.dart +++ b/lib/presentation/authentication/register_step2.dart @@ -73,7 +73,7 @@ class _RegisterNew extends State { onLanguageChanged: (lang) {}, hideLogoAndLang: true, ), - bottomSheet: Container( + bottomSheet: Container( // height: 200.h, width: MediaQuery.of(context).size.width, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -81,51 +81,52 @@ class _RegisterNew extends State { customBorder: BorderRadius.only(topLeft: Radius.circular(24), topRight: Radius.circular(24)), hasShadow: true, ), - child: Row( - children: [ - Expanded( - child: CustomButton( - text: LocaleKeys.cancel.tr(), - icon: AppAssets.cancel, - onPressed: () { - Navigator.of(context).pop(); - // authVM!.clearDefaultInputValues(); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - iconColor: AppColors.primaryRedColor, + child: Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.cancel.tr(), + icon: AppAssets.cancel, + onPressed: () { + Navigator.of(context).pop(); + // authVM!.clearDefaultInputValues(); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + iconColor: AppColors.primaryRedColor, + ), ), - ), - SizedBox(width: 16.w), - Expanded( - child: CustomButton( - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: AppColors.whiteColor, - text: LocaleKeys.confirm.tr(), - icon: AppAssets.confirm, - iconColor: AppColors.whiteColor, - onPressed: () { - if (appState.getUserRegistrationPayload.zipCode != CountryEnum.saudiArabia.countryCode) { - if (ValidationUtils.validateUaeRegistration( - name: authVM!.nameController.text, - gender: authVM!.genderType, - country: authVM!.pickedCountryByUAEUser, - maritalStatus: authVM!.maritalStatus, - onOkPress: () { - Navigator.of(context).pop(); - })) { + SizedBox(width: 16.w), + Expanded( + child: CustomButton( + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + text: LocaleKeys.confirm.tr(), + icon: AppAssets.confirm, + iconColor: AppColors.whiteColor, + onPressed: () { + if (appState.getUserRegistrationPayload.zipCode != CountryEnum.saudiArabia.countryCode) { + if (ValidationUtils.validateUaeRegistration( + name: authVM!.nameController.text, + gender: authVM!.genderType, + country: authVM!.pickedCountryByUAEUser, + maritalStatus: authVM!.maritalStatus, + onOkPress: () { + Navigator.of(context).pop(); + })) { + showModel(context: context); + } + } else { showModel(context: context); } - } else { - showModel(context: context); - } - }, - ), - ) - ], - ).paddingAll(16.h),), + }, + ), + ) + ], + ).paddingAll(16.h), + ), body: GestureDetector( onTap: () { FocusScope.of(context).unfocus(); diff --git a/lib/presentation/blood_donation/blood_donation_page.dart b/lib/presentation/blood_donation/blood_donation_page.dart index c6767c2d..b706d2cd 100644 --- a/lib/presentation/blood_donation/blood_donation_page.dart +++ b/lib/presentation/blood_donation/blood_donation_page.dart @@ -148,8 +148,8 @@ class _BloodDonationPageState extends State { children: [ LocaleKeys.city.tr().toText16(color: AppColors.textColor, isBold: true), (appState.isArabic() - ? (bloodDonationVM.selectedCity?.descriptionN ?? LocaleKeys.select.tr()) - : bloodDonationVM.selectedCity?.description ?? LocaleKeys.select.tr(context: context)) + ? (bloodDonationVM.selectedCity?.descriptionN ?? LocaleKeys.selectBloodDonationCity.tr()) + : bloodDonationVM.selectedCity?.description ?? LocaleKeys.selectBloodDonationCity.tr(context: context)) .toText14(color: AppColors.greyTextColor, isBold: true), ], ), @@ -176,8 +176,8 @@ class _BloodDonationPageState extends State { children: [ LocaleKeys.gender.tr().toText16(color: AppColors.textColor, isBold: true), (appState.isArabic() - ? (bloodDonationVM.selectedGender?.typeAr ?? LocaleKeys.select.tr()) - : bloodDonationVM.selectedGender?.type ?? LocaleKeys.select.tr(context: context)) + ? (bloodDonationVM.selectedGender?.typeAr ?? LocaleKeys.selectBloodDonationGender.tr()) + : bloodDonationVM.selectedGender?.type ?? LocaleKeys.selectBloodDonationGender.tr(context: context)) .toText14(color: AppColors.greyTextColor, isBold: true), ], ), @@ -208,8 +208,8 @@ class _BloodDonationPageState extends State { LocaleKeys.bloodType.tr().toText16(color: AppColors.textColor, isBold: true), // bloodDonationVM.selectedBloodType?.toText14(color: AppColors.greyTextColor, isBold: true), (appState.isArabic() - ? (bloodDonationVM.selectedBloodType ?? LocaleKeys.select.tr()) - : bloodDonationVM.selectedBloodType ?? LocaleKeys.select.tr(context: context)) + ? (bloodDonationVM.selectedBloodType ?? LocaleKeys.selectBloodType.tr()) + : bloodDonationVM.selectedBloodType ?? LocaleKeys.selectBloodType.tr(context: context)) .toText14(color: AppColors.greyTextColor, isBold: true) ], ), @@ -219,7 +219,7 @@ class _BloodDonationPageState extends State { ], ).onPress(() { showCommonBottomSheetWithoutHeight(context, - title: LocaleKeys.select.tr(context: context), + title: LocaleKeys.selectBloodGroup.tr(context: context), isDismissible: true, child: SelectBloodGroupWidget(isArabic: appState.isArabic(), bloodDonationViewModel: bloodDonationVM), callBackFunc: () {}); diff --git a/lib/presentation/blood_donation/widgets/select_gender_widget.dart b/lib/presentation/blood_donation/widgets/select_gender_widget.dart index 0d360a80..1368c4e3 100644 --- a/lib/presentation/blood_donation/widgets/select_gender_widget.dart +++ b/lib/presentation/blood_donation/widgets/select_gender_widget.dart @@ -37,7 +37,12 @@ class SelectGenderWidget extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, spacing: 8.h, - children: [GenderTypeEnum.values[index].name.toCamelCase.toText16(color: AppColors.textColor, isBold: true)], + children: [ + (isArabic + ? GenderTypeEnum.values[index].typeAr + : GenderTypeEnum.values[index].type) + .toText16(color: AppColors.textColor, isBold: true) + ], ), ), Transform.flip( @@ -52,7 +57,10 @@ class SelectGenderWidget extends StatelessWidget { ), ], ).paddingSymmetrical(16.h, 16.h).onPress(() { - bloodDonationViewModel.onGenderChange(GenderTypeEnum.values[index].name.toCamelCase); + final genderValue = isArabic + ? GenderTypeEnum.values[index].typeAr + : GenderTypeEnum.values[index].type; + bloodDonationViewModel.onGenderChange(genderValue); Navigator.of(context).pop(); })); }, diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index aff3e275..15b019e9 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -472,7 +472,7 @@ class _BookAppointmentPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.searchByClinic.tr(context: context).toText14(color: AppColors.textColor, isBold: true), - LocaleKeys.tapToSelectClinic.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), + LocaleKeys.tapToSelect.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), ], ), ], @@ -534,7 +534,7 @@ class _BookAppointmentPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.searchByRegion.tr(context: context).toText14(color: AppColors.textColor, isBold: true), - LocaleKeys.centralRegion.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), + LocaleKeys.tapToSelect.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), ], ), ], @@ -907,7 +907,7 @@ class _BookAppointmentPageState extends State { ), SizedBox(height: 36.h), CustomButton( - text: "Login to use this service", + text: LocaleKeys.loginToUseServiceLiveCare.tr(), onPressed: () async { await authVM.onLoginPressed(); }, diff --git a/lib/presentation/book_appointment/doctor_filter/doctors_filter.dart b/lib/presentation/book_appointment/doctor_filter/doctors_filter.dart index 02dd9b97..c09fbfef 100644 --- a/lib/presentation/book_appointment/doctor_filter/doctors_filter.dart +++ b/lib/presentation/book_appointment/doctor_filter/doctors_filter.dart @@ -1,6 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -59,7 +61,7 @@ class DoctorsFilters extends StatelessWidget{ Text( LocaleKeys.filters.tr(), style:TextStyle( - fontFamily: 'Poppins', + fontFamily: getIt.get().isArabic() ? 'CairoArabic' : 'Poppins', fontWeight: FontWeight.w600, fontSize: 27.f, color: AppColors.textColor, @@ -69,7 +71,7 @@ class DoctorsFilters extends StatelessWidget{ Text( LocaleKeys.clearAllFilters.tr(), style:TextStyle( - fontFamily: 'Poppins', + fontFamily: getIt.get().isArabic() ? 'CairoArabic' : 'Poppins', fontWeight: FontWeight.w600, fontSize: 14.f, color: AppColors.errorColor @@ -199,7 +201,7 @@ class DoctorsFilters extends StatelessWidget{ Text( title, style:TextStyle( - fontFamily: 'Poppins', + fontFamily: getIt.get().isArabic() ? 'CairoArabic' : 'Poppins', fontWeight: FontWeight.w600, fontSize: 16.f, color: AppColors.textColor, diff --git a/lib/presentation/book_appointment/doctor_profile_page.dart b/lib/presentation/book_appointment/doctor_profile_page.dart index dbb885b3..29e1c824 100644 --- a/lib/presentation/book_appointment/doctor_profile_page.dart +++ b/lib/presentation/book_appointment/doctor_profile_page.dart @@ -236,7 +236,7 @@ class DoctorProfilePage extends StatelessWidget { onSuccess: (dynamic respData) async { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( - title: LocaleKeys.pickADate.tr(), + title: LocaleKeys.pickADate.tr() , context, child: AppointmentCalendar(), isFullScreen: false, diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart index 1228e1e4..b3801113 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart @@ -240,6 +240,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { ], ).paddingSymmetrical(24.h, 0.h), (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0" || immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0.0") + // (true) ? CustomButton( text: LocaleKeys.confirmLiveCare.tr(context: context), onPressed: () async { diff --git a/lib/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart b/lib/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart index 870c8ad8..16c7d421 100644 --- a/lib/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart +++ b/lib/presentation/book_appointment/livecare/select_immediate_livecare_clinic_page.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; @@ -22,6 +23,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/widge import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; @@ -68,6 +70,34 @@ class _SelectImmediateLiveCareClinicPageState extends State(builder: (context, immediateLiveCareVM, child) { return Column( children: [ + TextInputWidget( + labelText: LocaleKeys.search.tr(context: context), + hintText: LocaleKeys.clinicName.tr(context: context), + controller: searchEditingController, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + focusNode: textFocusNode, + suffix: searchEditingController.text.isNotEmpty + ? GestureDetector( + onTap: () { + searchEditingController.clear(); + immediateLiveCareVM.filterClinic(""); + textFocusNode.unfocus(); + }, + child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown), + ) + : null, + onChange: (value) { + immediateLiveCareVM.filterClinic(value!); + }, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), + ), ListView.separated( padding: EdgeInsets.only(top: 16.h), shrinkWrap: true, diff --git a/lib/presentation/book_appointment/review_appointment_page.dart b/lib/presentation/book_appointment/review_appointment_page.dart index 689e6cf3..86702dbb 100644 --- a/lib/presentation/book_appointment/review_appointment_page.dart +++ b/lib/presentation/book_appointment/review_appointment_page.dart @@ -90,12 +90,14 @@ class _ReviewAppointmentPageState extends State { .toString() .toText16(isBold: true, maxlines: 1), SizedBox(width: 12.w), - (bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL != null && bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!.isNotEmpty )? Image.network( - bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png", - width: 20.h, - height: 15.h, - fit: BoxFit.cover, - ) : SizedBox.shrink(), + (bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL != null && bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL!.isNotEmpty) + ? Image.network( + bookAppointmentsViewModel.selectedDoctor.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png", + width: 20.h, + height: 15.h, + fit: BoxFit.cover, + ) + : SizedBox.shrink(), ], ), SizedBox(height: 2.h), @@ -162,7 +164,9 @@ class _ReviewAppointmentPageState extends State { runSpacing: 4.h, children: [ AppCustomChipWidget(labelText: "${appState.getAuthenticatedUser()!.age} ${LocaleKeys.yearsOld.tr(context: context)}"), - AppCustomChipWidget(labelText: "${LocaleKeys.gender.tr(context: context)}: ${appState.getAuthenticatedUser()?.gender == 1 ? LocaleKeys.malE.tr(context: context) : LocaleKeys.femaleGender.tr(context: context)}"), + AppCustomChipWidget( + labelText: + "${LocaleKeys.gender.tr(context: context)}: ${appState.getAuthenticatedUser()?.gender == 1 ? LocaleKeys.malE.tr(context: context) : LocaleKeys.femaleGender.tr(context: context)}"), ], ), ], @@ -191,19 +195,19 @@ class _ReviewAppointmentPageState extends State { bookAppointmentsViewModel.appointmentNearestGateResponseModel != null ? Wrap( direction: Axis.horizontal, - spacing: 8.w, - runSpacing: 8.h, - children: [ - AppCustomChipWidget( - labelText: bookAppointmentsVM.isAppointmentNearestGateLoading - ? "Floor: Ground Floor" + spacing: 8.w, + runSpacing: 8.h, + children: [ + AppCustomChipWidget( + labelText: bookAppointmentsVM.isAppointmentNearestGateLoading + ? "Floor: Ground Floor" : "${LocaleKeys.floor.tr(context: context)} ${getIt.get().isArabic() ? bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocationN : bookAppointmentsViewModel.appointmentNearestGateResponseModel!.clinicLocation}", ).toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), - AppCustomChipWidget( - labelText: + AppCustomChipWidget( + labelText: "${LocaleKeys.nearestGate.tr(context: context)} ${getIt.get().isArabic() ? bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumberN : bookAppointmentsVM.appointmentNearestGateResponseModel!.nearestGateNumber}") .toShimmer2(isShow: bookAppointmentsVM.isAppointmentNearestGateLoading), - ], + ], ) : SizedBox.shrink(), ], @@ -317,8 +321,8 @@ class _ReviewAppointmentPageState extends State { void initiateBookAppointment() async { // LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context)); - // LoaderBottomSheet.showLoader(loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context)); - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context)); + LoaderBottomSheet.showLoader( + loadingText: bookAppointmentsViewModel.isPatientRescheduleAppointment ? LocaleKeys.reschedulingAppo.tr(context: context) : LocaleKeys.bookingYourAppointment.tr(context: context)); myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); if (bookAppointmentsViewModel.isLiveCareSchedule) { @@ -329,23 +333,16 @@ class _ReviewAppointmentPageState extends State { LoaderBottomSheet.hideLoader(); await Future.delayed(Duration(milliseconds: 50)).then((value) async { // LoaderBottomSheet.showLoader(loadingText: LocaleKeys.appointmentSuccess.tr()); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()), - callBackFunc: () { - bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false); - bookAppointmentsViewModel.setIsLiveCareSchedule(false); - Navigator.pushAndRemoveUntil( - context, - CustomPageRoute( - page: LandingNavigation(), - ), - (r) => false); - }, - isFullScreen: false, - isCloseButtonVisible: false, - isAutoDismiss: true - ); + showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()), callBackFunc: () { + bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false); + bookAppointmentsViewModel.setIsLiveCareSchedule(false); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }, isFullScreen: false, isCloseButtonVisible: false, isAutoDismiss: true); }); }); } else { @@ -368,23 +365,11 @@ class _ReviewAppointmentPageState extends State { }, onSuccess: (apiResp) async { LoaderBottomSheet.hideLoader(); await Future.delayed(Duration(milliseconds: 50)).then((value) async { - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h), - callBackFunc: () { - bookAppointmentsViewModel.setIsLiveCareSchedule(false); - bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false); - Navigator.pushAndRemoveUntil( - context, - CustomPageRoute( - page: LandingNavigation(), - ), - (r) => false); - }, - isFullScreen: false, - isCloseButtonVisible: false, - isAutoDismiss: true - ); + showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr()).paddingSymmetrical(0.h, 24.h), callBackFunc: () { + bookAppointmentsViewModel.setIsLiveCareSchedule(false); + bookAppointmentsViewModel.setIsPatientRescheduleAppointment(false); + Navigator.pushAndRemoveUntil(context, CustomPageRoute(page: LandingNavigation()), (r) => false); + }, isFullScreen: false, isCloseButtonVisible: false, isAutoDismiss: true); }); }); } diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index e617194e..b8f44e78 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -55,12 +55,15 @@ class _SelectClinicPageState extends State { late AppointmentViaRegionViewmodel regionalViewModel; late AppState appState; late BookAppointmentsViewModel bookAppointmentsViewModel; + late String name; @override void initState() { scheduleMicrotask(() { bookAppointmentsViewModel.getClinics(); }); + + name = (widget.calculatorType?.name.toLowerCase() == "bmi" ? widget.calculatorType?.name.toUpperCase() : widget.calculatorType?.name.toCamelCase) ?? ""; super.initState(); } @@ -77,28 +80,29 @@ class _SelectClinicPageState extends State { appState = getIt.get(); if (widget.calculatorType != null) { return CollapsingListView( - title: "Your ${widget.calculatorType!.name.toCamelCase}", + title: appState.isArabic() ? widget.calculatorType!.displayName.toCamelCase : "Your ${widget.calculatorType!.displayName.toCamelCase}", bottomChild: 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: LocaleKeys.bookAppo.tr(context: context), - onPressed: () { - if (widget.calculatedResult["clinicId"] == null) { - bookAppointmentsViewModel.setIsDoctorsListLoading(true); - bookAppointmentsViewModel.isGetDocForHealthCal = true; - bookAppointmentsViewModel.calculationID = widget.calculatedResult["calculationID"]; - Navigator.push(context, CustomPageRoute(page: SelectDoctorPage())); - } else { - onClinicSelected(GetClinicsListResponseModel(clinicID: widget.calculatedResult["clinicId"], clinicDescription: "asdfds", clinicDescriptionN: "asdfds")); - } - }, - icon: null, - fontSize: 16.f, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - borderRadius: 12.r, - fontWeight: FontWeight.w600,), + text: LocaleKeys.bookAppo.tr(context: context), + onPressed: () { + if (widget.calculatedResult["clinicId"] == null) { + bookAppointmentsViewModel.setIsDoctorsListLoading(true); + bookAppointmentsViewModel.isGetDocForHealthCal = true; + bookAppointmentsViewModel.calculationID = widget.calculatedResult["calculationID"]; + Navigator.push(context, CustomPageRoute(page: SelectDoctorPage())); + } else { + onClinicSelected(GetClinicsListResponseModel(clinicID: widget.calculatedResult["clinicId"], clinicDescription: "asdfds", clinicDescriptionN: "asdfds")); + } + }, + icon: null, + fontSize: 16.f, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + borderRadius: 12.r, + fontWeight: FontWeight.w600, + ), ), child: getCalculatorResultWidget(type: widget.calculatorType!, calculatedResult: widget.calculatedResult).paddingSymmetrical(18.w, 24.h), ); @@ -266,6 +270,7 @@ class _SelectClinicPageState extends State { } Widget resultWidget({required HealthCalculatorsTypeEnum type, dynamic calculatedResult}) { + AppState appState = getIt(); return Column( children: [ if (type == HealthCalculatorsTypeEnum.bmi) ...[ @@ -655,7 +660,7 @@ class _SelectClinicPageState extends State { borderRadius: 8.h, textColor: AppColors.textColor), SizedBox(height: 16.h), - "Fertility Window".toText16(color: AppColors.textColor, isBold: true), + appState.isArabic() ? "فترة الخصوبة".toText16(color: AppColors.textColor, isBold: true) : "Fertility Window".toText16(color: AppColors.textColor, isBold: true), SizedBox(height: 8.h), Row( children: [ @@ -675,8 +680,11 @@ class _SelectClinicPageState extends State { ], ), SizedBox(height: 8.h), - "This is your fertile window. Maintaining a healthy calorie range supports your body during this period, considering your Basal Metabolic Rate and activity. Your Body Mass Index is within the healthy range." - .toText12(height: 1.5, color: AppColors.textColorLight, isBold: true) + appState.isArabic() + ? "هذه هي فترة خصوبتك. يساعد الحفاظ على نطاق صحي من السعرات الحرارية في دعم جسمك خلال هذه الفترة، مع مراعاة معدل الأيض الأساسي ومستوى نشاطك. مؤشر كتلة جسمك ضمن النطاق الصحي." + .toText12(height: 1.5, color: AppColors.textColorLight, isBold: true) + : "This is your fertile window. Maintaining a healthy calorie range supports your body during this period, considering your Basal Metabolic Rate and activity. Your Body Mass Index is within the healthy range." + .toText12(height: 1.5, color: AppColors.textColorLight, isBold: true) ], ), ), @@ -697,7 +705,7 @@ class _SelectClinicPageState extends State { ), Row( children: [ - "Tips to keep in mind".toText16(color: AppColors.textColor, isBold: true), + appState.isArabic() ? "نصائح هامة".toText16(color: AppColors.textColor, isBold: true) : "Tips to keep in mind".toText16(color: AppColors.textColor, isBold: true), ], ), ], @@ -705,13 +713,13 @@ class _SelectClinicPageState extends State { SizedBox(height: 8.h), Column( children: [ - _buildTipRow("Drink before you feel thirsty."), + _buildTipRow(appState.isArabic() ? "اشربي الماء قبل الشعور بالعطش" : "Drink before you feel thirsty."), SizedBox(height: 4.h), - _buildTipRow("Keep a refillable bottle next to you."), + _buildTipRow(appState.isArabic() ? "احتفظي بزجاجة ماء قابلة لإعادة التعبئة بجانبك" : "Keep a refillable bottle next to you."), SizedBox(height: 4.h), - _buildTipRow("Track your daily intake to stay motivated."), + _buildTipRow(appState.isArabic() ? "تابعي استهلاكك اليومي للحفاظ على حماسك" : "Track your daily intake to stay motivated."), SizedBox(height: 4.h), - _buildTipRow("Choose sparkling water instead of soda."), + _buildTipRow(appState.isArabic() ? "اختاري المياه الفوارة بدلاً من المشروبات الغازية" : "Choose sparkling water instead of soda."), ], ).paddingOnly(left: 4.w, right: 4.w) ], @@ -1375,6 +1383,7 @@ class _SelectClinicPageState extends State { } } } + bookAppointmentsViewModel.addListener(listener); if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION || regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) { diff --git a/lib/presentation/book_appointment/widgets/appointment_calendar.dart b/lib/presentation/book_appointment/widgets/appointment_calendar.dart index ef1cc273..9e83a70d 100644 --- a/lib/presentation/book_appointment/widgets/appointment_calendar.dart +++ b/lib/presentation/book_appointment/widgets/appointment_calendar.dart @@ -189,7 +189,7 @@ class _AppointmentCalendarState extends State { SizedBox(height: 16.h), CustomButton( - text: LocaleKeys.select.tr(context: context), + text: LocaleKeys.selectDateTimeKey.tr(context: context), isDisabled: dayEvents.isEmpty, onPressed: () async { if (appState.isAuthenticated) { diff --git a/lib/presentation/book_appointment/widgets/appointment_success_reminder_bottom_sheet.dart b/lib/presentation/book_appointment/widgets/appointment_success_reminder_bottom_sheet.dart new file mode 100644 index 00000000..ec3afc40 --- /dev/null +++ b/lib/presentation/book_appointment/widgets/appointment_success_reminder_bottom_sheet.dart @@ -0,0 +1,441 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/reminder_type.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; + +enum ReminderBottomSheetStep { + success, + reminderConfirmation, + reminderTypeSelection, + creatingReminder, + reminderSuccess, + reminderError, +} + +class AppointmentSuccessReminderBottomSheet extends StatefulWidget { + final PatientAppointmentHistoryResponseModel appointmentModel; + final MyAppointmentsViewModel myAppointmentsViewModel; + final VoidCallback onComplete; + + const AppointmentSuccessReminderBottomSheet({ + Key? key, + required this.appointmentModel, + required this.myAppointmentsViewModel, + required this.onComplete, + }) : super(key: key); + + @override + State createState() => _AppointmentSuccessReminderBottomSheetState(); +} + +class _AppointmentSuccessReminderBottomSheetState extends State { + ReminderBottomSheetStep currentStep = ReminderBottomSheetStep.success; + ReminderType? selectedReminderType; + String errorMessage = ''; + bool isProcessing = false; + + @override + void initState() { + super.initState(); + // Auto-progress to reminder confirmation after showing success + Future.delayed(Duration(milliseconds: 2000), () { + if (mounted) { + setState(() { + currentStep = ReminderBottomSheetStep.reminderConfirmation; + }); + } + }); + } + + void _handleNoReminder() { + Navigator.of(context).pop(); + widget.onComplete(); + } + + void _handleYesReminder() { + setState(() { + currentStep = ReminderBottomSheetStep.reminderTypeSelection; + }); + } + + void _selectReminderType(ReminderType type) { + setState(() { + selectedReminderType = type; + }); + } + + Future _createReminder() async { + if (selectedReminderType == null) return; + + setState(() { + currentStep = ReminderBottomSheetStep.creatingReminder; + isProcessing = true; + }); + + await widget.myAppointmentsViewModel.toggleReminder( + context: context, + shouldCreateReminder: true, + reminderType: selectedReminderType!, + patientAppointmentHistoryResponseModel: widget.appointmentModel, + onSuccess: (result) { + if (mounted) { + setState(() { + currentStep = ReminderBottomSheetStep.reminderSuccess; + isProcessing = false; + }); + // Auto-close after showing success + Future.delayed(Duration(milliseconds: 2000), () { + if (mounted) { + Navigator.of(context).pop(); + widget.onComplete(); + } + }); + } + }, + onError: (error) { + if (mounted) { + setState(() { + currentStep = ReminderBottomSheetStep.reminderError; + errorMessage = error; + isProcessing = false; + }); + } + }, + ); + } + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.vertical(top: Radius.circular(24.r)), + ), + padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 24.h), + child: AnimatedSwitcher( + duration: Duration(milliseconds: 300), + child: _buildStepContent(), + ), + ); + } + + Widget _buildStepContent() { + switch (currentStep) { + case ReminderBottomSheetStep.success: + return _buildSuccessStep(); + case ReminderBottomSheetStep.reminderConfirmation: + return _buildReminderConfirmationStep(); + case ReminderBottomSheetStep.reminderTypeSelection: + return _buildReminderTypeSelectionStep(); + case ReminderBottomSheetStep.creatingReminder: + return _buildCreatingReminderStep(); + case ReminderBottomSheetStep.reminderSuccess: + return _buildReminderSuccessStep(); + case ReminderBottomSheetStep.reminderError: + return _buildReminderErrorStep(); + } + } + + Widget _buildSuccessStep() { + return Column( + key: ValueKey('success'), + mainAxisSize: MainAxisSize.min, + children: [ + Utils.getSuccessWidget(loadingText: LocaleKeys.appointmentSuccess.tr(context: context)), + SizedBox(height: 16.h), + Center( + child: "Your appointment has been booked successfully!".toText14( + color: AppColors.greyTextColor, + ), + ), + ], + ); + } + + Widget _buildReminderConfirmationStep() { + return Column( + key: ValueKey('confirmation'), + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.notifications_active_outlined, + size: 56.h, + color: AppColors.primaryRedColor, + ), + SizedBox(height: 16.h), + Center(child: "Add Reminder?".toText20(isBold: true)), + SizedBox(height: 12.h), + Center( + child: "Would you like to add a reminder for this appointment?".toText14( + color: AppColors.greyTextColor, + ), + ), + SizedBox(height: 24.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.cancel.tr(context: context), + onPressed: _handleNoReminder, + backgroundColor: AppColors.whiteColor, + borderColor: AppColors.greyTextColor, + textColor: AppColors.greyTextColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12, + height: 48.h, + ), + ), + SizedBox(width: 12.w), + Expanded( + child: CustomButton( + text: LocaleKeys.confirm.tr(context: context), + onPressed: _handleYesReminder, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: Colors.white, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12, + height: 48.h, + ), + ), + ], + ), + ], + ); + } + + Widget _buildReminderTypeSelectionStep() { + return Column( + key: ValueKey('type_selection'), + mainAxisSize: MainAxisSize.min, + children: [ + Center(child: "Select Reminder Type".toText20(isBold: true)), + SizedBox(height: 8.h), + Center( + child: "Choose what type of reminder you want to set".toText14( + color: AppColors.greyTextColor, + ), + ), + SizedBox(height: 24.h), + _buildReminderTypeCard( + type: ReminderType.appointment, + icon: Icons.event_outlined, + title: "Appointment Reminder", + description: "Get reminded about your upcoming appointment", + ), + SizedBox(height: 12.h), + _buildReminderTypeCard( + type: ReminderType.payment, + icon: Icons.payment_outlined, + title: "Payment Reminder", + description: "Get reminded about payment for this appointment", + ), + SizedBox(height: 12.h), + _buildReminderTypeCard( + type: ReminderType.checkIn, + icon: Icons.check_circle_outline, + title: "Check-In Reminder", + description: "Get reminded to check in for your appointment", + ), + SizedBox(height: 24.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.cancel.tr(context: context), + onPressed: () { + setState(() { + currentStep = ReminderBottomSheetStep.reminderConfirmation; + selectedReminderType = null; + }); + }, + backgroundColor: AppColors.whiteColor, + borderColor: AppColors.greyTextColor, + textColor: AppColors.greyTextColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12, + height: 48.h, + ), + ), + SizedBox(width: 12.w), + Expanded( + child: CustomButton( + text: "Continue", + onPressed: selectedReminderType != null ? _createReminder : null, + backgroundColor: selectedReminderType != null ? AppColors.primaryRedColor : AppColors.greyTextColor.withAlpha(76), + borderColor: selectedReminderType != null ? AppColors.primaryRedColor : AppColors.greyTextColor.withAlpha(76), + textColor: Colors.white, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12, + height: 48.h, + ), + ), + ], + ), + ], + ); + } + + Widget _buildReminderTypeCard({ + required ReminderType type, + required IconData icon, + required String title, + required String description, + }) { + final isSelected = selectedReminderType == type; + return GestureDetector( + onTap: () => _selectReminderType(type), + child: Container( + padding: EdgeInsets.all(16.h), + decoration: BoxDecoration( + color: isSelected ? AppColors.primaryRedColor.withAlpha(26) : AppColors.scaffoldBgColor, + border: Border.all( + color: isSelected ? AppColors.primaryRedColor : AppColors.borderOnlyColor, + width: isSelected ? 2 : 1, + ), + borderRadius: BorderRadius.circular(12.r), + ), + child: Row( + children: [ + Container( + padding: EdgeInsets.all(12.h), + decoration: BoxDecoration( + color: isSelected ? AppColors.primaryRedColor : AppColors.greyTextColor.withAlpha(26), + borderRadius: BorderRadius.circular(8.r), + ), + child: Icon( + icon, + color: isSelected ? Colors.white : AppColors.greyTextColor, + size: 24.h, + ), + ), + SizedBox(width: 12.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.toText16( + isBold: true, + color: isSelected ? AppColors.primaryRedColor : AppColors.textColor, + ), + SizedBox(height: 4.h), + description.toText12( + color: AppColors.greyTextColor, + ), + ], + ), + ), + if (isSelected) + Icon( + Icons.check_circle, + color: AppColors.primaryRedColor, + size: 24.h, + ), + ], + ), + ), + ); + } + + Widget _buildCreatingReminderStep() { + return Column( + key: ValueKey('creating'), + mainAxisSize: MainAxisSize.min, + children: [ + CircularProgressIndicator(color: AppColors.primaryRedColor), + SizedBox(height: 24.h), + Center( + child: "Creating your reminder...".toText16( + isBold: true, + ), + ), + SizedBox(height: 8.h), + Center( + child: "Please wait while we set up your reminder".toText14( + color: AppColors.greyTextColor, + ), + ), + ], + ); + } + + Widget _buildReminderSuccessStep() { + return Column( + key: ValueKey('reminder_success'), + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.check_circle, + size: 64.h, + color: AppColors.successColor, + ), + SizedBox(height: 16.h), + Center( + child: LocaleKeys.reminderSuccess.tr(context: context).toText20( + isBold: true, + ), + ), + SizedBox(height: 8.h), + Center( + child: "Your ${selectedReminderType?.displayName ?? 'reminder'} has been set successfully!".toText14( + color: AppColors.greyTextColor, + ), + ), + ], + ); + } + + Widget _buildReminderErrorStep() { + return Column( + key: ValueKey('error'), + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.error_outline, + size: 64.h, + color: AppColors.primaryRedColor, + ), + SizedBox(height: 16.h), + Center( + child: "Error".toText20( + isBold: true, + color: AppColors.primaryRedColor, + ), + ), + SizedBox(height: 8.h), + Center( + child: errorMessage.toText14( + color: AppColors.greyTextColor, + ), + ), + SizedBox(height: 24.h), + CustomButton( + text: "Close", + onPressed: () { + Navigator.of(context).pop(); + widget.onComplete(); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: Colors.white, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12, + height: 48.h, + ), + ], + ); + } +} + diff --git a/lib/presentation/book_appointment/widgets/bell_switch.dart b/lib/presentation/book_appointment/widgets/bell_switch.dart new file mode 100644 index 00000000..c002e59a --- /dev/null +++ b/lib/presentation/book_appointment/widgets/bell_switch.dart @@ -0,0 +1,192 @@ +import 'package:flutter/material.dart'; + +class BellAnimatedSwitch extends StatefulWidget { + final bool initialValue; + final ValueChanged? onChanged; + final Color? activeColor; + final Color? inactiveColor; + final Color? activeIconColor; + final Color? inactiveIconColor; + final Color? activeCircleColor; + final Color? inactiveCircleColor; + final Widget? activeIcon; + final Widget? inactiveIcon; + final double width; + final double height; + final Duration animationDuration; + + const BellAnimatedSwitch({ + super.key, + this.initialValue = false, + this.onChanged, + this.activeColor, + this.inactiveColor, + this.activeIconColor, + this.inactiveIconColor, + this.activeCircleColor, + this.inactiveCircleColor, + this.activeIcon, + this.inactiveIcon, + this.width = 48, + this.height = 30, + this.animationDuration = const Duration(milliseconds: 300), + }); + + @override + State createState() => BellAnimatedSwitchState(); +} + +class BellAnimatedSwitchState extends State with SingleTickerProviderStateMixin { + late bool _isActive; + late AnimationController _animationController; + late Animation _circleAlignment; + late Animation _scaleAnimation; + + @override + void initState() { + super.initState(); + _isActive = widget.initialValue; + _animationController = AnimationController( + duration: widget.animationDuration, + vsync: this, + ); + + _circleAlignment = AlignmentTween(begin: Alignment.centerLeft, end: Alignment.centerRight).animate( + CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), + ); + + // Scale animation for tap feedback + _scaleAnimation = Tween(begin: 1.0, end: 0.9).animate( + CurvedAnimation( + parent: _animationController, + curve: const Interval(0.0, 0.5, curve: Curves.easeOut), + ), + ); + + if (_isActive) { + _animationController.value = 1.0; + } + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + /// Public method to set switch value programmatically without triggering onChanged callback + void setSwitchValue(bool value, {bool animate = true}) { + if (_isActive == value) return; // No change needed + + setState(() { + _isActive = value; + if (animate) { + if (_isActive) { + _animationController.forward(); + } else { + _animationController.reverse(); + } + } else { + _animationController.value = _isActive ? 1.0 : 0.0; + } + }); + } + + void _toggle() { + setState(() { + _isActive = !_isActive; + if (_isActive) { + _animationController.forward(); + } else { + _animationController.reverse(); + } + widget.onChanged?.call(_isActive); + }); + } + + Color _getBackgroundColor() { + if (_isActive) { + return widget.activeColor ?? Colors.green.shade400; + } + return widget.inactiveColor ?? Colors.grey.shade300; + } + + Color _getCircleColor() { + if (_isActive) { + return widget.activeCircleColor ?? Colors.white; + } + return widget.inactiveCircleColor ?? Colors.white; + } + + Widget _getIcon() { + if (_isActive) { + return widget.activeIcon ?? + Icon( + Icons.check, + size: 18, + color: widget.activeIconColor ?? Colors.green.shade400, + ); + } + return widget.inactiveIcon ?? + Icon( + Icons.close, + size: 18, + color: widget.inactiveIconColor ?? Colors.grey.shade600, + ); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: _toggle, + child: AnimatedBuilder( + animation: _animationController, + builder: (context, child) { + return Transform.scale( + scale: _scaleAnimation.value, + child: Container( + width: widget.width, + height: widget.height, + decoration: BoxDecoration( + color: _getBackgroundColor(), + borderRadius: BorderRadius.circular(widget.height / 2), + // boxShadow: [ + // BoxShadow( + // color: Colors.black.withAlpha(26), + // blurRadius: 4, + // offset: const Offset(0, 2), + // ), + // ], + ), + child: Stack( + children: [ + // Animated circle with icon inside + Align( + alignment: _circleAlignment.value, + child: Container( + margin: const EdgeInsets.all(4), + width: widget.height - 8, + height: widget.height - 8, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _getCircleColor(), + boxShadow: [ + BoxShadow( + color: Colors.black.withAlpha(51), + blurRadius: 2, + offset: const Offset(0, 1), + ), + ], + ), + child: Center(child: _getIcon()), + ), + ), + ], + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart b/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart index 589764c1..0761ab6c 100644 --- a/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart +++ b/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart @@ -250,7 +250,10 @@ class _ComprehensiveCheckupPageState extends State { physics: NeverScrollableScrollPhysics(), itemBuilder: (context, index) { final service = services[index]; - final serviceName = service.text ?? service.textN ?? ''; + final appStateLocal = getIt.get(); + final serviceName = appStateLocal.isArabic() + ? (service.textN ?? service.text ?? '') + : (service.text ?? service.textN ?? ''); final price = service.priceTotal ?? 0.0; return RadioListTileWidget( value: service.iD ?? 0, diff --git a/lib/presentation/contact_us/feedback_page.dart b/lib/presentation/contact_us/feedback_page.dart index cc70f28e..8ca6619c 100644 --- a/lib/presentation/contact_us/feedback_page.dart +++ b/lib/presentation/contact_us/feedback_page.dart @@ -475,12 +475,12 @@ class FeedbackPage extends StatelessWidget { ), SizedBox(height: 8.h), "${contactUsViewModel.cocItemsList[index].cOCTitle}" - .toText16(weight: FontWeight.bold, fontFamily: Utils.isArabicText(contactUsViewModel.cocItemsList[index].cOCTitle ?? "") ? "GESSTwo" : "Poppins"), + .toText16(weight: FontWeight.bold, fontFamily: Utils.isArabicText(contactUsViewModel.cocItemsList[index].cOCTitle ?? "") ? "CairoArabic" : "Poppins"), SizedBox(height: 8.h), "${contactUsViewModel.cocItemsList[index].detail}".toText14( weight: FontWeight.w600, color: AppColors.textColorLight, - fontFamily: Utils.isArabicText(contactUsViewModel.cocItemsList[index].detail ?? "") ? "GESSTwo" : "Poppins"), + fontFamily: Utils.isArabicText(contactUsViewModel.cocItemsList[index].detail ?? "") ? "CairoArabic" : "Poppins"), ], ), ).paddingOnly(bottom: 16.h), diff --git a/lib/presentation/e_referral/new_e_referral.dart b/lib/presentation/e_referral/new_e_referral.dart index 8631fa62..b9d54942 100644 --- a/lib/presentation/e_referral/new_e_referral.dart +++ b/lib/presentation/e_referral/new_e_referral.dart @@ -193,7 +193,7 @@ class _NewReferralPageState extends State { children: List.generate(3, (index) { // Fill current step and all previous completed steps if (index <= _currentStep) { - return StepperWidget(widthOfOneState, AppColors.primaryRedColor, true, 4.h); + return StepperWidget(widthOfOneState, AppColors.primaryRedColor, index == _currentStep, 4.h); } else { return StepperWidget(widthOfOneState, AppColors.greyLightColor, false, 4.h); } diff --git a/lib/presentation/e_referral/widget/e_referral_other_details.dart b/lib/presentation/e_referral/widget/e_referral_other_details.dart index 579f281f..c30b67c3 100644 --- a/lib/presentation/e_referral/widget/e_referral_other_details.dart +++ b/lib/presentation/e_referral/widget/e_referral_other_details.dart @@ -142,8 +142,8 @@ class _OtherDetailsStepState extends State { Widget _buildBranchField(BuildContext context, ReferralFormManager formManager) { return DropdownWidget( - labelText: 'Branch', - hintText: formManager.formData.branch?.desciption ?? "Select Branch", + labelText: LocaleKeys.branch.tr(), + hintText: formManager.formData.branch?.desciption ?? LocaleKeys.selectBranch.tr(), isEnable: false, hasSelectionCustomIcon: true, labelColor: Colors.black, diff --git a/lib/presentation/e_referral/widget/e_referral_patient_info.dart b/lib/presentation/e_referral/widget/e_referral_patient_info.dart index cbce6919..a30c0c3e 100644 --- a/lib/presentation/e_referral/widget/e_referral_patient_info.dart +++ b/lib/presentation/e_referral/widget/e_referral_patient_info.dart @@ -1,9 +1,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'dart:ui' as ui; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/utils/validation_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_all_cities_resp_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/e_referral/e_referral_form_manager.dart'; import 'package:provider/provider.dart'; @@ -13,6 +16,8 @@ import 'package:hmg_patient_app_new/widgets/dropdown/dropdown_widget.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import '../../../core/dependencies.dart' show getIt; + class PatientInformationStep extends StatefulWidget { const PatientInformationStep({super.key}); @@ -138,37 +143,40 @@ class PatientInformationStepState extends State { } Widget _buildPatientPhoneField(ReferralFormManager formManager) { - return Focus( - focusNode: _phoneFocusNode, - child: TextInputWidget( - labelText: LocaleKeys.phoneNumber.tr(context: context), - hintText: "5xxxxxxxx", - controller: _phoneController, - padding: const EdgeInsets.all(8), - keyboardType: TextInputType.number, - fontFamily: "Poppins", - onChange: (value) { - formManager.updatePatientPhone(value ?? ''); - }, - onCountryChange: (value) { - formManager.updatePatientCountryEnum(value); - }, - prefix: '966', - isBorderAllowed: false, - isAllowLeadingIcon: true, - fontSize: 13, - isCountryDropDown: true, - leadingIcon: AppAssets.smart_phone, - errorMessage: formManager.errors.patientPhone, - hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.patientPhone) - ).paddingSymmetrical(0, 8), + return Directionality( + textDirection: ui.TextDirection.ltr, + child: Focus( + focusNode: _phoneFocusNode, + child: TextInputWidget( + labelText: LocaleKeys.phoneNumber.tr(context: context), + hintText: "5xxxxxxxx", + controller: _phoneController, + padding: const EdgeInsets.all(8), + keyboardType: TextInputType.number, + fontFamily: "Poppins", + onChange: (value) { + formManager.updatePatientPhone(value ?? ''); + }, + onCountryChange: (value) { + formManager.updatePatientCountryEnum(value); + }, + prefix: '966', + isBorderAllowed: false, + isAllowLeadingIcon: true, + fontSize: 13, + isCountryDropDown: true, + leadingIcon: AppAssets.smart_phone, + errorMessage: formManager.errors.patientPhone, + hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.patientPhone) + ).paddingSymmetrical(0, 8), + ), ); } Widget _buildPatientCityField(BuildContext context, ReferralFormManager formManager) { return DropdownWidget( - labelText: 'City', - hintText: formManager.formData.patientCity?.description ?? LocaleKeys.selectCity.tr(context: context), + labelText: LocaleKeys.city.tr(), + hintText: getLocaleDescription(formManager.formData.patientCity) ?? LocaleKeys.selectCity.tr(context: context), isEnable: false, hasSelectionCustomIcon: true, labelColor: Colors.black, @@ -183,6 +191,10 @@ class PatientInformationStepState extends State { }); } + String? getLocaleDescription(GetAllCitiesResponseModel? patientCity){ + return getIt.get().isArabic() ? patientCity?.descriptionN: patientCity?.description; + } + void _showCityBottomSheet(BuildContext context, ReferralFormManager formManager) { @@ -213,7 +225,7 @@ class PatientInformationStepState extends State { itemBuilder: (context, index) { final city = cities[index]; return ListTile( - title: (city.description ?? 'Unknown').toText14(), + title: (getLocaleDescription(city) ?? 'Unknown').toText14(), onTap: () { formManager.updatePatientCity(city); Navigator.pop(context); diff --git a/lib/presentation/e_referral/widget/e_referral_requester_form.dart b/lib/presentation/e_referral/widget/e_referral_requester_form.dart index d57abe23..926aab4f 100644 --- a/lib/presentation/e_referral/widget/e_referral_requester_form.dart +++ b/lib/presentation/e_referral/widget/e_referral_requester_form.dart @@ -116,9 +116,9 @@ class RequesterFormStepState extends State { Widget _buildRelationshipField(BuildContext context, ReferralFormManager formManager) { return DropdownWidget( labelText: LocaleKeys.relationship.tr(context: context), - hintText: formManager.formData.relationship?.textEn ?? LocaleKeys.selectRelation.tr(context: context), + hintText: formManager.formData.relationship?.text ?? LocaleKeys.selectRelation.tr(context: context), isEnable: false, - selectedValue: formManager.formData.relationship?.textEn ?? LocaleKeys.selectRelation.tr(context: context), + selectedValue: formManager.formData.relationship?.text ?? LocaleKeys.selectRelation.tr(context: context), errorMessage: formManager.errors.relationship, hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.relationship), hasSelectionCustomIcon: false, @@ -182,7 +182,7 @@ class RequesterFormStepState extends State { itemBuilder: (context, index) { final relationship = hmgServicesVM.relationTypes[index]; return ListTile( - title:relationship.textEn?.toText14(), + title:relationship.text?.toText14(), onTap: () { formManager.updateRelationship(relationship); Navigator.pop(context); diff --git a/lib/presentation/e_referral/widget/search_e_referral_form.dart b/lib/presentation/e_referral/widget/search_e_referral_form.dart index 33697d5e..c19b8a40 100644 --- a/lib/presentation/e_referral/widget/search_e_referral_form.dart +++ b/lib/presentation/e_referral/widget/search_e_referral_form.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/utils/validation_utils.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/e_referral/e_referral_form_manager.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -69,12 +72,13 @@ class SearchEReferralFormFormState extends State { autofocus: true, child: TextInputWidget( controller: _searchController, - padding: const EdgeInsets.symmetric(horizontal: 16.0), - hintText: formManager.searchCriteria == 0 ? "Enter Identification Number" : "Enter Referral Number", - labelText: formManager.searchCriteria == 0 ? "Identification Number" : "Referral Number", + hintText: formManager.searchCriteria == 0 ? LocaleKeys.enterIdentificationNumber.tr() : LocaleKeys.enterReferralNumber.tr(), + labelText: formManager.searchCriteria == 0 ? LocaleKeys.identificationNumber.tr() : LocaleKeys.referralNumber.tr(), keyboardType: TextInputType.number, fontFamily: "Poppins", errorMessage: formManager.errors.searchValue, + padding: EdgeInsets.symmetric(vertical: 14.h, horizontal: 16.h), + hasError: !ValidationUtils.isNullOrEmpty(formManager.errors.searchValue), onChange: (value) { formManager.updateSearchValue(value ?? ''); @@ -90,10 +94,10 @@ class SearchEReferralFormFormState extends State { autofocus: false, child: TextInputWidget( autoFocus: false, - labelText: 'Phone Number', - hintText: "5xxxxxxxx", + labelText: LocaleKeys.phoneNumber.tr(), + hintText: LocaleKeys.phoneHint.tr(), controller: _phoneController, - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), keyboardType: TextInputType.number, fontFamily: "Poppins", onChange: (value) { @@ -122,8 +126,8 @@ class SearchEReferralFormFormState extends State { Widget _buildSelectionField(BuildContext context, ReferralFormManager formManager) { return DropdownWidget( - labelText: "Select the Search Criteria", - hintText: formManager.searchCriteria == 0 ? "Identification Number" : "Referral Number", + labelText: LocaleKeys.selectCriteria.tr(), + hintText: formManager.searchCriteria == 0 ? LocaleKeys.identificationNumber.tr() : LocaleKeys.referralNumber.tr(), isEnable: false, hasSelectionCustomIcon: false, labelColor: Colors.black, @@ -137,13 +141,13 @@ class SearchEReferralFormFormState extends State { void _showCriteriaBottomSheet(BuildContext context, ReferralFormManager formManager) { final criteriaList = [ - {0: 'Identification Number'}, - {1: 'Referral Number'}, + {0: LocaleKeys.identificationNumber.tr()}, + {1: LocaleKeys.referralNumber.tr()}, ]; showCommonBottomSheetWithoutHeight( context, - title: "Select Criteria", + title: LocaleKeys.selectCriteria.tr(), child: ListView.separated( shrinkWrap: true, physics: const BouncingScrollPhysics(), diff --git a/lib/presentation/emergency_services/RRT/rrt_map_screen.dart b/lib/presentation/emergency_services/RRT/rrt_map_screen.dart index bd5407b6..1a603dc3 100644 --- a/lib/presentation/emergency_services/RRT/rrt_map_screen.dart +++ b/lib/presentation/emergency_services/RRT/rrt_map_screen.dart @@ -559,7 +559,9 @@ class RrtMapScreen extends StatelessWidget { context, child: SizedBox( height: MediaQuery.sizeOf(context).height * .8, - child: LocationInputBottomSheet(), + child: LocationInputBottomSheet( + moveCameraController:(location) => context.read().moveController(location) , + ), ), isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart b/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart index 3b095ecf..79172db3 100644 --- a/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart +++ b/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart @@ -140,7 +140,7 @@ class RrtRequestTypeSelect extends StatelessWidget { ), CustomButton(text: LocaleKeys.next.tr(), onPressed: () { Navigator.pop(context); - emergencyServicesVM.openRRT(); + emergencyServicesVM.openRRT(context); }, isDisabled: !emergencyServicesVM.agreedToTermsAndCondition, ) diff --git a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart index 55958048..c891e565 100644 --- a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart +++ b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart @@ -53,8 +53,8 @@ class CallAmbulancePage extends StatelessWidget { ExpandableBottomSheet( bottomSheetType: context.watch().bottomSheetType, children: { - BottomSheetType.EXPANDED: ExpanedBottomSheet(context), - BottomSheetType.FIXED: FixedBottomSheet(context), + BottomSheetType.EXPANDED: ExpandedBottomSheet(context), + BottomSheetType.FIXED: pickUpDetailAndAppointementSection(context), }, ), ], @@ -92,7 +92,7 @@ class CallAmbulancePage extends StatelessWidget { ); } - Widget FixedBottomSheet(BuildContext context) { + Widget ExpandedBottomSheet(BuildContext context) { return GestureDetector( onVerticalDragUpdate: (details) { // if(details.delta.dy<0){ @@ -172,7 +172,11 @@ class CallAmbulancePage extends StatelessWidget { CustomButton( text: LocaleKeys.selectDetails.tr(context: context), onPressed: () { - context.read().updateBottomSheetState(BottomSheetType.EXPANDED); + LocationViewModel locationViewModel = context.read(); + GeocodeResponse? response = locationViewModel.geocodeResponse; + PlaceDetails? placeDetails = locationViewModel.placeDetails; + PlacePrediction? placePrediction = locationViewModel.selectedPrediction; + context.read().submitAmbulanceRequest(response?.results.first, placeDetails, placePrediction); }) ], ).paddingOnly(top: 24.h, bottom: 32.h, left: 24.h, right: 24.h), @@ -185,7 +189,7 @@ class CallAmbulancePage extends StatelessWidget { ); } - Widget ExpanedBottomSheet(BuildContext context) { + Widget pickUpDetailAndAppointementSection(BuildContext context) { return GestureDetector( onVerticalDragUpdate: (details) { if (details.delta.dy > 0) { @@ -324,28 +328,34 @@ class CallAmbulancePage extends StatelessWidget { ) ], ), + if(AmbulanceCallingPlace.TO_HOSPITAL == context.read().callingPlace) + Row( + children: [ + hospitalAndPickUpItemContent( + title: LocaleKeys.appointment.tr(context: context), + subTitle: LocaleKeys.haveAnyAppointment.tr(context: context), + leadingIcon: AppAssets.appointment_calendar_icon, + ), + CustomSwitch( + value: context.watch().haveAnAppointment, + onChanged: (value) async { + // if (value) { + // openAppointmentList(context); + // } + await context.read().updateAppointment(value); + if (context.read().appointments?.isNotEmpty == true) { + openAppointmentList(context); + } + }, + ) + ], + ), - Row( - children: [ - hospitalAndPickUpItemContent( - title: LocaleKeys.appointment.tr(context: context), - subTitle: LocaleKeys.haveAnyAppointment.tr(context: context), - leadingIcon: AppAssets.appointment_calendar_icon, - ), - CustomSwitch( - value: context.watch().haveAnAppointment, - onChanged: (value) async { - // if (value) { - // openAppointmentList(context); - // } - await context.read().updateAppointment(value); - if (context.read().appointments?.isNotEmpty == true) { - openAppointmentList(context); - } - }, - ) - ], - ), + + // Visibility( + // visible: , + // child: + // ), ], ).paddingAll(16.h), ); @@ -460,12 +470,8 @@ class CallAmbulancePage extends StatelessWidget { CustomButton( text: LocaleKeys.submitRequest.tr(context: context), onPressed: () { - LocationViewModel locationViewModel = context.read(); - GeocodeResponse? response = locationViewModel.geocodeResponse; - PlaceDetails? placeDetails = locationViewModel.placeDetails; - PlacePrediction? placePrediction = locationViewModel.selectedPrediction; - context.read().submitAmbulanceRequest(response?.results.first, placeDetails, placePrediction); - }) + context.read().updateBottomSheetState(BottomSheetType.EXPANDED); + }) ], ).paddingOnly(top: 24.h, bottom: 12.h, left: 24.h, right: 24.h), ), @@ -571,7 +577,9 @@ class CallAmbulancePage extends StatelessWidget { context, child: SizedBox( height: MediaQuery.sizeOf(context).height * .8, - child: LocationInputBottomSheet(), + child: LocationInputBottomSheet( + moveCameraController:(location) => context.read().moveController(location) , + ), ), isFullScreen: false, isCloseButtonVisible: true, @@ -582,21 +590,24 @@ class CallAmbulancePage extends StatelessWidget { } hospitalField(BuildContext context) { - return DecoratedBox( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), - child: Row( - children: [ - hospitalAndPickUpItemContent( - title: LocaleKeys.selectHospital.tr(context: context), - subTitle: context.read().getSelectedHospitalName() ?? LocaleKeys.selectHospital.tr(context: context), - leadingIcon: AppAssets.hospital, - ), - Utils.buildSvgWithAssets(icon: AppAssets.down_cheveron, width: 24.h, height: 24.h).paddingAll(16.h) - ], - ).onPress(() { - showHospitalBottomSheet(context); - }).paddingSymmetrical( - 10.w, 12.h), + return Visibility( + visible: false == context.watch().haveAnAppointment, + child: DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), + child: Row( + children: [ + hospitalAndPickUpItemContent( + title: LocaleKeys.selectHospital.tr(context: context), + subTitle: context.read().getSelectedHospitalName() ?? LocaleKeys.selectHospital.tr(context: context), + leadingIcon: AppAssets.hospital, + ), + Utils.buildSvgWithAssets(icon: AppAssets.down_cheveron, width: 24.h, height: 24.h).paddingAll(16.h) + ], + ).onPress(() { + showHospitalBottomSheet(context); + }).paddingSymmetrical( + 10.w, 12.h), + ), ); } diff --git a/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart b/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart index 0f4c587b..a714ab56 100644 --- a/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart +++ b/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart @@ -73,7 +73,10 @@ class TrackingScreen extends StatelessWidget { child: SingleChildScrollView( child: Column( children: [ - animationSection(), + Padding( + padding: EdgeInsetsDirectional.only(top:16.h,start: 16.w,end: 16.w ), + child: animationSection(context), + ), Column( spacing: 16.h, children: [orderStatus(context), orderTrackingId(context), contactSection()], @@ -83,12 +86,12 @@ class TrackingScreen extends StatelessWidget { ); } - animationSection() { + animationSection(BuildContext context) { switch (state) { case OrderTrackingState.waitingForCall: case OrderTrackingState.dispactched: case OrderTrackingState.returning: - return Lottie.asset(AppAnimations.ambulance, repeat: true, reverse: false, width: 260.h, height: 200.h); + return showImage(order != null ? AppAssets.ambulance_section : AppAssets.rrt_section, context); case OrderTrackingState.ended: return Lottie.asset(AppAnimations.checkmark, repeat: true, width: 260.h, height: 200.h); case OrderTrackingState.failed: @@ -146,7 +149,7 @@ class TrackingScreen extends StatelessWidget { borderColor: AppColors.primaryRedColor, textColor: Colors.white, icon: AppAssets.cancel, - height: 40.h, + height: 56.h, iconSize: 18.w, ), ], @@ -237,7 +240,7 @@ class TrackingScreen extends StatelessWidget { backgroundColor: AppColors.lightRedButtonColor, iconColor: AppColors.primaryRedColor, borderColor: Colors.transparent, - height: 40.h, + height: 56.h, ), ) ], @@ -270,7 +273,7 @@ class TrackingScreen extends StatelessWidget { informationRegardingTrackingSection() { return Column( crossAxisAlignment: CrossAxisAlignment.start, - children: [currentStatus(), SizedBox(height: 4.h), "You will receive a call from HMG for confirmation ".toText12(color: AppColors.greyTextColor, isBold: true)], + children: [currentStatus(), SizedBox(height: 4.h)], ); } @@ -489,6 +492,18 @@ class TrackingScreen extends StatelessWidget { ); } + showImage(String imageUrl, BuildContext context) { + return Utils.buildImgWithAssetsWithBorderRadius( + icon: imageUrl, + iconColor: Colors.transparent, + width: MediaQuery.sizeOf(context).width, + height: 380.h, + borderRadius: 24.r, + border: 1.r/// did this as the function that is being called is wrongly coupled with the border radius and border, and in some cases like the ambulance and rrt section we don't need any + /// border or border radius + ); + } + // Widget rrtOrderData() { // return Row( // spacing: 8.h, diff --git a/lib/presentation/emergency_services/emergency_services_page.dart b/lib/presentation/emergency_services/emergency_services_page.dart index 3d5470a8..2d4ef828 100644 --- a/lib/presentation/emergency_services/emergency_services_page.dart +++ b/lib/presentation/emergency_services/emergency_services_page.dart @@ -90,7 +90,7 @@ class _EmergencyServicesPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.callAmbulance.tr().toText16(isBold: true, color: AppColors.blackColor), - LocaleKeys.requestAmbulanceInEmergency.tr().toText12(color: AppColors.greyTextColor, isBold: true), + LocaleKeys.requestAmbulanceInEmergency.tr().toText12(color: AppColors.greyTextColor, isBold: true), ], ), ), diff --git a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart index bb9f3ffd..3c8a4770 100644 --- a/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart +++ b/lib/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart @@ -49,18 +49,21 @@ class ErOnlineCheckinHome extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.immediate_service_icon, width: 58.h, height: 58.h), - SizedBox(width: 18.h), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.onlineCheckIn.tr().toText18(color: AppColors.textColor, isBold: true), - LocaleKeys.erOnlineCheckInDescription.tr().toText14(color: AppColors.greyTextColor, isBold: true), - ], - ), - ), + Utils.getNoDataWidget(context) + // Utils.buildSvgWithAssets(icon: AppAssets.immediate_service_icon, width: 58.h, height: 58.h), + // SizedBox(width: 18.h), + // Expanded( + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // LocaleKeys.onlineCheckIn.tr().toText18(color: AppColors.textColor, isBold: true), + // LocaleKeys.erOnlineCheckInDescription.tr().toText14(color: AppColors.greyTextColor, isBold: true), + // ], + // ), + // ), ], ), ], diff --git a/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart b/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart index 67bdcbd8..52d820a3 100644 --- a/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart +++ b/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart @@ -42,7 +42,7 @@ class AmbulanceHistoryItem extends StatelessWidget { Row( spacing: 4.w, children: [ - chip(Utils.getDayMonthYearDateFormatted(DateUtil.convertStringToDate(order.time)), AppAssets.calendar, AppColors.blackBgColor), + chip(Utils.getDayMonthYearDateFormatted(DateUtil.parseISODate(order.created)), AppAssets.calendar, AppColors.blackBgColor), chip(LocaleKeys.ambulancerequest.tr(context: context), AppAssets.ambulance, AppColors.blackBgColor), ], ), diff --git a/lib/presentation/emergency_services/history/widget/rrt_item.dart b/lib/presentation/emergency_services/history/widget/rrt_item.dart index c85f12a5..d3c0e340 100644 --- a/lib/presentation/emergency_services/history/widget/rrt_item.dart +++ b/lib/presentation/emergency_services/history/widget/rrt_item.dart @@ -42,7 +42,7 @@ class RRTItem extends StatelessWidget { Row( spacing: 4.w, children: [ - chip(Utils.getDayMonthYearDateFormatted(DateUtil.convertStringToDate(order.time)), AppAssets.calendar, AppColors.blackBgColor), + chip(Utils.getDayMonthYearDateFormatted(DateUtil.parseISODate(order.created)), AppAssets.calendar, AppColors.blackBgColor), chip(LocaleKeys.rapidResponseTeam.tr(context: context), AppAssets.ic_rrt_vehicle, AppColors.blackBgColor), ], ), diff --git a/lib/presentation/emergency_services/nearest_er_page.dart b/lib/presentation/emergency_services/nearest_er_page.dart index ea9a1517..97e601d5 100644 --- a/lib/presentation/emergency_services/nearest_er_page.dart +++ b/lib/presentation/emergency_services/nearest_er_page.dart @@ -92,7 +92,7 @@ class _NearestErPageState extends State { }, ); } else { - return Center(child: Utils.getNoDataWidget(context, noDataText: "No nearest Er Around you")); + return Center(child: Utils.getNoDataWidget(context, noDataText: LocaleKeys.noNearestER.tr())); } }), ), diff --git a/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart b/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart index 4b66abd9..9b1209c8 100644 --- a/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart +++ b/lib/presentation/emergency_services/widgets/location_input_bottom_sheet.dart @@ -23,8 +23,9 @@ import '../../../theme/colors.dart'; class LocationInputBottomSheet extends StatelessWidget { final Debouncer debouncer = Debouncer(milliseconds: 500); + final Function(Location)? moveCameraController; - LocationInputBottomSheet({super.key}); + LocationInputBottomSheet({super.key, this.moveCameraController}); @override Widget build(BuildContext context) { @@ -106,7 +107,7 @@ class LocationInputBottomSheet extends StatelessWidget { Navigator.of(context).pop(); var location = context.read().placeDetails; if(location != null) { - context.read().moveController( + moveCameraController?.call( Location(lat: location.lat, lng: location.lng)); } }); diff --git a/lib/presentation/health_calculators_and_converts/health_calculators_page.dart b/lib/presentation/health_calculators_and_converts/health_calculators_page.dart index bba6f705..8b351ecf 100644 --- a/lib/presentation/health_calculators_and_converts/health_calculators_page.dart +++ b/lib/presentation/health_calculators_and_converts/health_calculators_page.dart @@ -14,6 +14,7 @@ import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/expandable_list_widget.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; class HealthCalculatorsPage extends StatefulWidget { @@ -37,73 +38,89 @@ class _HealthCalculatorsPageState extends State { return CollapsingListView( title: widget.type == HealthCalConEnum.calculator ? LocaleKeys.healthCalculators.tr(context: context) : LocaleKeys.healthConverters.tr(), child: widget.type == HealthCalConEnum.calculator - ? Column( - children: [ - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r), - child: Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.general_health, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false), - SizedBox(width: 12.w), - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - LocaleKeys.generalHealth.tr().toText16(isBold: true), - LocaleKeys.relatedToBMICalories.tr().toText12(isBold: true, color: Color(0xFF8F9AA3)) - ], - ), - ), - SizedBox( - width: 12.w, - ), - Transform.flip( - flipX: getIt.get().isArabic(), - child: Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor)), - ], - ).paddingAll(16.w)) - .onPress(() { - dialogService.showFamilyBottomSheetWithoutHWithChild( - label: LocaleKeys.selectCalculator.tr(), - message: "", - child: showCalculatorsItems(type: HealthCalculatorEnum.general), - onOkPressed: () {}, - ); - }), - SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r), - child: Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.women_health, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false), - SizedBox(width: 12.w), - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - LocaleKeys.womensHealth.tr().toText16(isBold: true), - LocaleKeys.relatedToPeriodsOvulation.tr().toText12(isBold: true, color: Color(0xFF8F9AA3)) - ], - ), - ), - SizedBox(width: 12.w), - Transform.flip( - flipX: getIt.get().isArabic(), - child: Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor)), - ], - ).paddingAll(16.w)) - .onPress(() { - dialogService.showFamilyBottomSheetWithoutHWithChild( - label: LocaleKeys.selectCalculator.tr(), - message: "", - child: showCalculatorsItems(type: HealthCalculatorEnum.women), - onOkPressed: () {}, - ); - }), + ? CustomExpandableList( + expansionMode: ExpansionMode.exactlyOne, + dividerColor: AppColors.dividerColor, + itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h), + items: [ + ExpandableListItem( + title: LocaleKeys.womensHealth.tr(context: context).toText18(isBold: true), + expandedBackgroundColor: Colors.transparent, + initiallyExpanded: true, + children: [showCalculatorsItems(type: HealthCalculatorEnum.women)], + ), + ExpandableListItem( + title: LocaleKeys.generalHealth.tr(context: context).toText18(isBold: true), + expandedBackgroundColor: Colors.transparent, + initiallyExpanded: true, + children: [showCalculatorsItems(type: HealthCalculatorEnum.general)], + ), ], - ).paddingSymmetrical(20.w, 24.h) + theme: ExpandableListTheme.custom( + defaultTrailingIcon: Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 22.h, width: 22.w, iconColor: AppColors.textColor), + ), + ).paddingSymmetrical(16.w, 0.0) + // ? Column( + // children: [ + // Container( + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r), + // child: Row( + // children: [ + // Utils.buildSvgWithAssets(icon: AppAssets.general_health, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false), + // SizedBox(width: 12.w), + // Flexible( + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // mainAxisAlignment: MainAxisAlignment.center, + // children: [LocaleKeys.generalHealth.tr().toText16(isBold: true), LocaleKeys.relatedToBMICalories.tr().toText12(isBold: true, color: Color(0xFF8F9AA3))], + // ), + // ), + // SizedBox( + // width: 12.w, + // ), + // Transform.flip( + // flipX: getIt.get().isArabic(), + // child: Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor)), + // ], + // ).paddingAll(16.w)) + // .onPress(() { + // dialogService.showFamilyBottomSheetWithoutHWithChild( + // label: LocaleKeys.selectCalculator.tr(), + // message: "", + // child: showCalculatorsItems(type: HealthCalculatorEnum.general), + // onOkPressed: () {}, + // ); + // }), + // SizedBox(height: 16.h), + // Container( + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r), + // child: Row( + // children: [ + // Utils.buildSvgWithAssets(icon: AppAssets.women_health, height: 40.h, width: 40.w, fit: BoxFit.none, applyThemeColor: false), + // SizedBox(width: 12.w), + // Flexible( + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // mainAxisAlignment: MainAxisAlignment.center, + // children: [LocaleKeys.womensHealth.tr().toText16(isBold: true), LocaleKeys.relatedToPeriodsOvulation.tr().toText12(isBold: true, color: Color(0xFF8F9AA3))], + // ), + // ), + // SizedBox(width: 12.w), + // Transform.flip( + // flipX: getIt.get().isArabic(), + // child: Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor)), + // ], + // ).paddingAll(16.w)) + // .onPress(() { + // dialogService.showFamilyBottomSheetWithoutHWithChild( + // label: LocaleKeys.selectCalculator.tr(), + // message: "", + // child: showCalculatorsItems(type: HealthCalculatorEnum.women), + // onOkPressed: () {}, + // ); + // }), + // ], + // ).paddingSymmetrical(20.w, 24.h) : Column( children: [ Container( @@ -118,8 +135,8 @@ class _HealthCalculatorsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - LocaleKeys.bloodSugar.tr().toText16(isBold: true), - LocaleKeys.trackYourGlucoseLevels.tr().toText12( + LocaleKeys.bloodSugar.tr(context: context).toText16(isBold: true), + LocaleKeys.trackYourGlucoseLevels.tr(context: context).toText12( isBold: true, color: Color(0xFF8F9AA3), ) @@ -129,7 +146,10 @@ class _HealthCalculatorsPageState extends State { SizedBox( width: 12.w, ), - Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor), + Transform.flip( + flipX: getIt.get().isArabic(), + child: Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, + fit: BoxFit.contain, iconColor: AppColors.textColor),), ], ).paddingAll(16.w)) .onPress(() { @@ -151,16 +171,14 @@ class _HealthCalculatorsPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, - children: [ - LocaleKeys.bloodCholesterol.tr().toText16(isBold: true), - LocaleKeys.monitorCholesterolLevels.tr() - .toText12(isBold: true, color: Color(0xFF8F9AA3)) - ], + children: [LocaleKeys.bloodCholesterol.tr(context: context).toText16(isBold: true), LocaleKeys.monitorCholesterolLevels.tr(context: context).toText12(isBold: true, color: Color(0xFF8F9AA3))], ), ), SizedBox(width: 12.w), - Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor), - ], + Transform.flip( + flipX: getIt.get().isArabic(), + child: Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, + fit: BoxFit.contain, iconColor: AppColors.textColor),), ], ).paddingAll(16.w)) .onPress(() { Navigator.of(context).push( @@ -182,15 +200,16 @@ class _HealthCalculatorsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - LocaleKeys.triglyceridesFatBlood.tr().toText16(isBold: true), - LocaleKeys.understandTriglyceridesImpact.tr() - .toText12(isBold: true, color: Color(0xFF8F9AA3)) + LocaleKeys.triglyceridesFatBlood.tr(context: context).toText16(isBold: true), + LocaleKeys.understandTriglyceridesImpact.tr(context: context).toText12(isBold: true, color: Color(0xFF8F9AA3)) ], ), ), SizedBox(width: 12.w), - Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.contain, iconColor: AppColors.textColor), - ], + Transform.flip( + flipX: getIt.get().isArabic(), + child: Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, + fit: BoxFit.contain, iconColor: AppColors.textColor),), ], ).paddingAll(16.w)) .onPress(() { Navigator.of(context).push( @@ -204,49 +223,104 @@ class _HealthCalculatorsPageState extends State { } Widget showCalculatorsItems({required HealthCalculatorEnum type}) { - return GridView.builder( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, // 4 icons per row - crossAxisSpacing: 16.w, - mainAxisSpacing: 16.w, - childAspectRatio: 0.85), - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: type == HealthCalculatorEnum.general ? generalHealthServices.length : womenHealthServices.length, - padding: EdgeInsets.zero, - itemBuilder: (BuildContext context, int index) { - return HealthCard( - icon: type == HealthCalculatorEnum.general ? generalHealthServices[index].icon : womenHealthServices[index].icon, - labelText: type == HealthCalculatorEnum.general ? generalHealthServices[index].title : womenHealthServices[index].title, - onTap: () { - Navigator.pop(context); - Navigator.of(context).push( - CustomPageRoute( - page: HealthCalculatorDetailedPage( - calculatorType: type == HealthCalculatorEnum.general ? generalHealthServices[index].type : womenHealthServices[index].type, - clinicID: type == HealthCalculatorEnum.general ? generalHealthServices[index].clinicID : womenHealthServices[index].clinicID, - calculationID: type == HealthCalculatorEnum.general ? generalHealthServices[index].calculationID : womenHealthServices[index].calculationID, + return ListView.separated( + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: type == HealthCalculatorEnum.general ? generalHealthServices.length : womenHealthServices.length, + padding: EdgeInsets.zero, + itemBuilder: (BuildContext context, int index) { + return HealthCard( + icon: type == HealthCalculatorEnum.general ? generalHealthServices[index].icon : womenHealthServices[index].icon, + labelText: type == HealthCalculatorEnum.general ? generalHealthServices[index].title : womenHealthServices[index].title, + subtitle: type == HealthCalculatorEnum.general ? generalHealthServices[index].subTitle ?? "" : womenHealthServices[index].subTitle ?? "", + iconColor: type == HealthCalculatorEnum.general ? generalHealthServices[index].iconColor : womenHealthServices[index].iconColor, + iconBgColor: type == HealthCalculatorEnum.general ? generalHealthServices[index].bgColor : womenHealthServices[index].bgColor, + onTap: () { + Navigator.of(context).push( + CustomPageRoute( + page: HealthCalculatorDetailedPage( + calculatorType: type == HealthCalculatorEnum.general ? generalHealthServices[index].type : womenHealthServices[index].type, + clinicID: type == HealthCalculatorEnum.general ? generalHealthServices[index].clinicID : womenHealthServices[index].clinicID, + calculationID: type == HealthCalculatorEnum.general ? generalHealthServices[index].calculationID : womenHealthServices[index].calculationID, + ), ), - ), - ); - }, - ); - }, - ); + ); + }, + ); + }, + separatorBuilder: (BuildContext context, int index) { + return SizedBox(height: 16.h); + }); } final List generalHealthServices = [ - HealthComponentModel(title: LocaleKeys.bmiCalculator.tr(), icon: AppAssets.bmi, type: HealthCalculatorsTypeEnum.bmi, clinicID: 108, calculationID: null), - HealthComponentModel(title: LocaleKeys.caloriesCalculator.tr(), icon: AppAssets.calories, type: HealthCalculatorsTypeEnum.calories, clinicID: null, calculationID: 2), - HealthComponentModel(title: LocaleKeys.bmrCalculator.tr(), icon: AppAssets.bmr, type: HealthCalculatorsTypeEnum.bmr, clinicID: null, calculationID: 3), - HealthComponentModel(title: LocaleKeys.idealBodyWeight.tr(), icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.idealBodyWeight, clinicID: null, calculationID: 4), - HealthComponentModel(title: LocaleKeys.bodyFatCalculator.tr(), icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.bodyFat, clinicID: null, calculationID: 5), - HealthComponentModel(title: LocaleKeys.carbsProteinFat.tr(), icon: AppAssets.ibw, type: HealthCalculatorsTypeEnum.crabsProteinFat, clinicID: null, calculationID: 11), + HealthComponentModel( + title: LocaleKeys.bmiCalculator.tr(), + subTitle: LocaleKeys.bmiCalculatorDesc.tr(), + icon: AppAssets.bmicalc, + type: HealthCalculatorsTypeEnum.bmi, + clinicID: 108, + bgColor: AppColors.bmiCalculatorColor, + calculationID: null), + HealthComponentModel( + title: LocaleKeys.caloriesCalculator.tr(), + subTitle: LocaleKeys.caloriesCalculatorDesc.tr(), + icon: AppAssets.bmicalc, + type: HealthCalculatorsTypeEnum.calories, + clinicID: null, + bgColor: AppColors.caloriesCalculatorColor, + calculationID: 2), + HealthComponentModel( + title: LocaleKeys.bmrCalculator.tr(), + subTitle: LocaleKeys.bmrCalculatorDesc.tr(), + icon: AppAssets.bmrcalc, + type: HealthCalculatorsTypeEnum.bmr, + clinicID: null, + bgColor: AppColors.bmrCalculatorColor, + calculationID: 3), + HealthComponentModel( + title: LocaleKeys.idealBodyWeight.tr(), + subTitle: LocaleKeys.idealBodyWeightDesc.tr(), + icon: AppAssets.idealbodyweightcacl, + type: HealthCalculatorsTypeEnum.idealBodyWeight, + bgColor: AppColors.idealBodyWeightColor, + clinicID: null, + calculationID: 4), + HealthComponentModel( + title: LocaleKeys.bodyFatCalculator.tr(), + subTitle: LocaleKeys.bodyFatCalculatorDesc.tr(), + icon: AppAssets.bodyfatcalc, + type: HealthCalculatorsTypeEnum.bodyFat, + clinicID: null, + bgColor: AppColors.bodyFatCalculatorColor, + calculationID: 5), + HealthComponentModel( + title: LocaleKeys.carbsProteinFat.tr(), + subTitle: LocaleKeys.carbsProteinFatDesc.tr(), + icon: AppAssets.bmicalc, + type: HealthCalculatorsTypeEnum.crabsProteinFat, + clinicID: null, + bgColor: AppColors.carbsProteinFatColor, + calculationID: 11), ]; final List womenHealthServices = [ - HealthComponentModel(title: LocaleKeys.ovulationPeriod.tr(), icon: AppAssets.locate_me, type: HealthCalculatorsTypeEnum.ovulation, clinicID: null, calculationID: 6 ), - HealthComponentModel(title: LocaleKeys.deliveryDueDate.tr(), icon: AppAssets.activeCheck, type: HealthCalculatorsTypeEnum.deliveryDueDate, clinicID: null, calculationID: 6), + HealthComponentModel( + title: LocaleKeys.ovulationPeriod.tr(), + subTitle: LocaleKeys.ovulationPeriodDesc.tr(), + icon: AppAssets.ovluationperiod, + type: HealthCalculatorsTypeEnum.ovulation, + clinicID: null, + calculationID: 6, + bgColor: AppColors.ovulationPeriodColor), + HealthComponentModel( + title: LocaleKeys.deliveryDueDate.tr(), + subTitle: LocaleKeys.deliveryDueDateDesc.tr(), + icon: AppAssets.duedatecalc, + type: HealthCalculatorsTypeEnum.deliveryDueDate, + clinicID: null, + bgColor: AppColors.deliveryDueDateColor, + calculationID: 6), ]; } diff --git a/lib/presentation/health_calculators_and_converts/widgets/bf.dart b/lib/presentation/health_calculators_and_converts/widgets/bf.dart index d71566a2..a2d804b0 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/bf.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/bf.dart @@ -26,7 +26,19 @@ class _BodyFatWidgetState extends State { final TextEditingController _neckController = TextEditingController(); final TextEditingController _waistController = TextEditingController(); final TextEditingController _hipController = TextEditingController(); - final List _heightUnits = ['cm', 'm', 'ft', 'in']; + + final Map _heightUnits = { + 'cm': LocaleKeys.cm, + 'm': LocaleKeys.m, + 'ft': LocaleKeys.ft, + 'in': LocaleKeys.inch, + }; + + final Map _genders = { + 'Male': LocaleKeys.malE, + 'Female': LocaleKeys.femaleGender, + }; + String selectedHeightUnit = 'cm'; String selectedNeckUnit = 'in'; String selectedWaistUnit = 'in'; @@ -82,6 +94,7 @@ class _BodyFatWidgetState extends State { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), child: Column( children: [ + // === GENDER ROW === Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -95,7 +108,7 @@ class _BodyFatWidgetState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ LocaleKeys.selectGender.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), - selectedGender.toText12(isBold: true, color: AppColors.textColor), + (_genders[selectedGender] ?? selectedGender).tr().toText12(isBold: true, color: AppColors.textColor), ], ), ], @@ -105,7 +118,6 @@ class _BodyFatWidgetState extends State { SizedBox(width: 4.w), ], ).paddingSymmetrical(0.w, 16.w).onPress(() { - List _genders = ["Male", "Female"]; dialogService.showFamilyBottomSheetWithoutHWithChild( label: LocaleKeys.selectGender.tr(context: context), message: "", @@ -116,7 +128,8 @@ class _BodyFatWidgetState extends State { shrinkWrap: true, itemCount: _genders.length, itemBuilder: (context, index) { - final unit = _genders[index]; + final key = _genders.keys.elementAt(index); + final localeKey = _genders[key]!; return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, @@ -127,7 +140,7 @@ class _BodyFatWidgetState extends State { spacing: 8.h, children: [ Radio( - value: unit, + value: key, groupValue: selectedGender, activeColor: AppColors.errorColor, onChanged: (String? value) { @@ -140,13 +153,13 @@ class _BodyFatWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toCamelCase.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + localeKey.tr().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { - selectedGender = unit; - _onInputChanged(); + selectedGender = key; }); + _onInputChanged(); Navigator.pop(context); }), ), @@ -162,6 +175,7 @@ class _BodyFatWidgetState extends State { ); }), Divider(height: 1, color: Color(0xFFEEEEEE)), + // === HEIGHT ROW === Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -208,72 +222,24 @@ class _BodyFatWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor), - selectedHeightUnit.toText12(isBold: true, color: AppColors.textColor), + LocaleKeys.unit.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), + (_heightUnits[selectedHeightUnit] ?? selectedHeightUnit).tr().toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), Utils.buildSvgWithAssets(icon: AppAssets.arrow_down) ], ).onPress(() { - dialogService.showFamilyBottomSheetWithoutHWithChild( - label: LocaleKeys.unit.tr(context: context), - message: "", - child: Container( - padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), - decoration: BoxDecoration(color: AppColors.whiteColor, borderRadius: BorderRadius.circular(20.r)), - child: ListView.separated( - shrinkWrap: true, - itemCount: _heightUnits.length, - itemBuilder: (context, index) { - final unit = _heightUnits[index]; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - SizedBox( - height: 70, - child: Row( - spacing: 8.h, - children: [ - Radio( - value: unit, - groupValue: selectedHeightUnit, - activeColor: AppColors.errorColor, - onChanged: (String? value) { - if (value == null) return; - setState(() { - selectedHeightUnit = value; - }); - Navigator.pop(context); - }, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, - ], - ).onPress(() { - setState(() { - selectedHeightUnit = unit; - _onInputChanged(); - }); - Navigator.pop(context); - }), - ), - ], - ); - }, - separatorBuilder: (BuildContext context, int index) { - return Divider(height: 1, color: Color(0xFFEEEEEE)); - }, - ), - ), - onOkPressed: () {}, - ); + _showUnitBottomSheet(context, dialogService, selectedHeightUnit, (value) { + setState(() { selectedHeightUnit = value; }); + _onInputChanged(); + }); }), ), ], ).paddingSymmetrical(0.w, 16.w), Divider(height: 1, color: Color(0xFFEEEEEE)), + // === NECK ROW === Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -302,12 +268,7 @@ class _BodyFatWidgetState extends State { hintText: '7', hintStyle: TextStyle(color: Colors.grey), ), - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: Colors.black87, - height: 1.0, - ), + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.0), ), ) ], @@ -326,71 +287,23 @@ class _BodyFatWidgetState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ LocaleKeys.unit.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), - selectedNeckUnit.toText12(isBold: true, color: AppColors.textColor), + (_heightUnits[selectedNeckUnit] ?? selectedNeckUnit).tr().toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), Utils.buildSvgWithAssets(icon: AppAssets.arrow_down) ], ).onPress(() { - dialogService.showFamilyBottomSheetWithoutHWithChild( - label: LocaleKeys.unit.tr(context: context), - message: "", - child: Container( - padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), - decoration: BoxDecoration(color: AppColors.whiteColor, borderRadius: BorderRadius.circular(20.r)), - child: ListView.separated( - shrinkWrap: true, - itemCount: _heightUnits.length, - itemBuilder: (context, index) { - final unit = _heightUnits[index]; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - SizedBox( - height: 70, - child: Row( - spacing: 8.h, - children: [ - Radio( - value: unit, - groupValue: selectedNeckUnit, - activeColor: AppColors.errorColor, - onChanged: (String? value) { - if (value == null) return; - setState(() { - selectedNeckUnit = value; - }); - Navigator.pop(context); - }, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, - ], - ).onPress(() { - setState(() { - selectedNeckUnit = unit; - _onInputChanged(); - }); - Navigator.pop(context); - }), - ), - ], - ); - }, - separatorBuilder: (BuildContext context, int index) { - return Divider(height: 1, color: Color(0xFFEEEEEE)); - }, - ), - ), - onOkPressed: () {}, - ); + _showUnitBottomSheet(context, dialogService, selectedNeckUnit, (value) { + setState(() { selectedNeckUnit = value; }); + _onInputChanged(); + }); }), ), ], ).paddingSymmetrical(0.w, 16.w), Divider(height: 1, color: Color(0xFFEEEEEE)), + // === WAIST ROW === Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -419,12 +332,7 @@ class _BodyFatWidgetState extends State { hintText: '30', hintStyle: TextStyle(color: Colors.grey), ), - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: Colors.black87, - height: 1.0, - ), + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.0), ), ) ], @@ -442,72 +350,24 @@ class _BodyFatWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor), - selectedWaistUnit.toText12(isBold: true, color: AppColors.textColor), + LocaleKeys.unit.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), + (_heightUnits[selectedWaistUnit] ?? selectedWaistUnit).tr().toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), Utils.buildSvgWithAssets(icon: AppAssets.arrow_down) ], ).onPress(() { - dialogService.showFamilyBottomSheetWithoutHWithChild( - label: LocaleKeys.unit.tr(context: context), - message: "", - child: Container( - padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), - decoration: BoxDecoration(color: AppColors.whiteColor, borderRadius: BorderRadius.circular(20.r)), - child: ListView.separated( - shrinkWrap: true, - itemCount: _heightUnits.length, - itemBuilder: (context, index) { - final unit = _heightUnits[index]; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - SizedBox( - height: 70, - child: Row( - spacing: 8.h, - children: [ - Radio( - value: unit, - groupValue: selectedWaistUnit, - activeColor: AppColors.errorColor, - onChanged: (String? value) { - if (value == null) return; - setState(() { - selectedWaistUnit = value; - }); - Navigator.pop(context); - }, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, - ], - ).onPress(() { - setState(() { - selectedWaistUnit = unit; - _onInputChanged(); - }); - Navigator.pop(context); - }), - ), - ], - ); - }, - separatorBuilder: (BuildContext context, int index) { - return Divider(height: 1, color: Color(0xFFEEEEEE)); - }, - ), - ), - onOkPressed: () {}, - ); + _showUnitBottomSheet(context, dialogService, selectedWaistUnit, (value) { + setState(() { selectedWaistUnit = value; }); + _onInputChanged(); + }); }), ), ], ).paddingSymmetrical(0.w, 16.w), Divider(height: 1, color: Color(0xFFEEEEEE)), + // === HIP ROW === Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -536,12 +396,7 @@ class _BodyFatWidgetState extends State { hintText: '0', hintStyle: TextStyle(color: Colors.grey), ), - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: Colors.black87, - height: 1.0, - ), + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, height: 1.0), ), ) ], @@ -560,66 +415,17 @@ class _BodyFatWidgetState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ LocaleKeys.unit.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), - selectedHipUnit.toText12(isBold: true, color: AppColors.textColor), + (_heightUnits[selectedHipUnit] ?? selectedHipUnit).tr().toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), Utils.buildSvgWithAssets(icon: AppAssets.arrow_down) ], ).onPress(() { - dialogService.showFamilyBottomSheetWithoutHWithChild( - label: LocaleKeys.unit.tr(context: context), - message: "", - child: Container( - padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), - decoration: BoxDecoration(color: AppColors.whiteColor, borderRadius: BorderRadius.circular(20.r)), - child: ListView.separated( - shrinkWrap: true, - itemCount: _heightUnits.length, - itemBuilder: (context, index) { - final unit = _heightUnits[index]; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - SizedBox( - height: 70, - child: Row( - spacing: 8.h, - children: [ - Radio( - value: unit, - groupValue: selectedHipUnit, - activeColor: AppColors.errorColor, - onChanged: (String? value) { - if (value == null) return; - setState(() { - selectedHipUnit = value; - }); - Navigator.pop(context); - }, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, - ], - ).onPress(() { - setState(() { - selectedHipUnit = unit; - _onInputChanged(); - }); - Navigator.pop(context); - }), - ), - ], - ); - }, - separatorBuilder: (BuildContext context, int index) { - return Divider(height: 1, color: Color(0xFFEEEEEE)); - }, - ), - ), - onOkPressed: () {}, - ); + _showUnitBottomSheet(context, dialogService, selectedHipUnit, (value) { + setState(() { selectedHipUnit = value; }); + _onInputChanged(); + }); }), ), ], @@ -631,7 +437,7 @@ class _BodyFatWidgetState extends State { Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w), SizedBox(width: 12.w), Expanded( - child: "Estimate the total body fat based on the size.".toText12(isBold: true, color: AppColors.inputLabelTextColor), + child: LocaleKeys.bodyFatAlert.tr().toText12(isBold: true, color: AppColors.inputLabelTextColor), ), ], ).paddingSymmetrical(0.w, 16.w), @@ -640,4 +446,56 @@ class _BodyFatWidgetState extends State { ); }); } + + void _showUnitBottomSheet(BuildContext context, DialogService dialogService, String currentValue, Function(String) onSelected) { + dialogService.showFamilyBottomSheetWithoutHWithChild( + label: LocaleKeys.unit.tr(context: context), + message: "", + child: Container( + padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), + decoration: BoxDecoration(color: AppColors.whiteColor, borderRadius: BorderRadius.circular(20.r)), + child: ListView.separated( + shrinkWrap: true, + itemCount: _heightUnits.length, + itemBuilder: (context, index) { + final key = _heightUnits.keys.elementAt(index); + final localeKey = _heightUnits[key]!; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox( + height: 70, + child: Row( + spacing: 8.h, + children: [ + Radio( + value: key, + groupValue: currentValue, + activeColor: AppColors.errorColor, + onChanged: (String? value) { + if (value == null) return; + onSelected(value); + Navigator.pop(context); + }, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + localeKey.tr().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + ], + ).onPress(() { + onSelected(key); + Navigator.pop(context); + }), + ), + ], + ); + }, + separatorBuilder: (BuildContext context, int index) { + return Divider(height: 1, color: Color(0xFFEEEEEE)); + }, + ), + ), + onOkPressed: () {}, + ); + } } diff --git a/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart b/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart index 53bdf1bf..14b956e0 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/blood_cholesterol.dart @@ -1,4 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; @@ -76,7 +78,7 @@ class _BloodCholesterolWidgetState extends State { child: Column( children: [ _buildInputField( - label: "MG/DL", + label: LocaleKeys.mldl.tr(), hint: "120", controller: _mgdlController, focusNode: _mgdlFocus, @@ -105,7 +107,7 @@ class _BloodCholesterolWidgetState extends State { ], ), _buildInputField( - label: "MMOL/L", + label:LocaleKeys.mmol.tr(), hint: "3.1", controller: _mmolController, focusNode: _mmolFocus, @@ -121,7 +123,7 @@ class _BloodCholesterolWidgetState extends State { Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w), SizedBox(width: 12.w), Expanded( - child: "Convert blood cholesterol values between mg/dL and mmol/L. ".toText12(isBold: true, color: AppColors.inputLabelTextColor), + child:LocaleKeys.convertBloodcholesterolInfo.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), ), ], ).paddingSymmetrical(0.w, 16.w), diff --git a/lib/presentation/health_calculators_and_converts/widgets/blood_sugar.dart b/lib/presentation/health_calculators_and_converts/widgets/blood_sugar.dart index f2110646..670236de 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/blood_sugar.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/blood_sugar.dart @@ -1,4 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; @@ -129,7 +131,7 @@ class _BloodSugarWidgetState extends State { ), SizedBox(width: 12.w), Expanded( - child: "Convert blood glucose values between mg/dL and mmol/L. (1 mmol/L ≈ 18.0182 mg/dL)".toText12( + child: LocaleKeys.glucoseAlert.tr().toText12( isBold: true, color: AppColors.inputLabelTextColor, ), diff --git a/lib/presentation/health_calculators_and_converts/widgets/bmi.dart b/lib/presentation/health_calculators_and_converts/widgets/bmi.dart index 9e9d1bed..b982df0b 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/bmi.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/bmi.dart @@ -25,11 +25,29 @@ class BMIWidget extends StatefulWidget { class _BMIWidgetState extends State { final TextEditingController _heightController = TextEditingController(); final TextEditingController _weightController = TextEditingController(); - final List _heightUnits = ['cm', 'm', 'ft', 'in']; - final List _weightUnits = ['kg', 'lb']; + // Map keys are the localization keys (when available) or plain strings. + // Map values are the actual unit values used for calculations. + final Map _heightUnitsMap = { + LocaleKeys.cm: 'cm', + LocaleKeys.m: 'm', // no LocaleKeys entry found for meter, fallback to key 'm' + LocaleKeys.ft: 'ft', + LocaleKeys.inch: 'in', // no LocaleKeys entry for inches, fallback + }; + + final Map _weightUnitsMap = { + LocaleKeys.kg: 'kg', + LocaleKeys.lb: 'lb', + }; String selectedHeightUnit = 'cm'; String selectedWeightUnit = 'kg'; + // Helper to return a localized label for a given selected (value) unit. + String _getUnitLabel(Map map, String value) { + final entry = map.entries.firstWhere((e) => e.value == value, orElse: () => MapEntry(value, value)); + // tr() will return the localized string if available, otherwise it will return the key itself. + return entry.key.tr(context: context); + } + @override void initState() { super.initState(); @@ -75,7 +93,7 @@ class _BMIWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Height".toText12(isBold: true, color: AppColors.inputLabelTextColor), + LocaleKeys.height.tr().toText12(isBold: true, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -110,8 +128,9 @@ class _BMIWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor), - selectedHeightUnit.toUpperCase().toText12(isBold: true, color: AppColors.textColor), + LocaleKeys.unit.tr().toText12(isBold: true, color: AppColors.inputLabelTextColor), + // Show localized label for the currently selected height unit + _getUnitLabel(_heightUnitsMap, selectedHeightUnit).toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), @@ -126,9 +145,11 @@ class _BMIWidgetState extends State { decoration: BoxDecoration(color: AppColors.whiteColor, borderRadius: BorderRadius.circular(20.r)), child: ListView.separated( shrinkWrap: true, - itemCount: _heightUnits.length, + itemCount: _heightUnitsMap.length, itemBuilder: (context, index) { - final unit = _heightUnits[index]; + final entry = _heightUnitsMap.entries.elementAt(index); + final key = entry.key; + final value = entry.value; return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, @@ -139,7 +160,7 @@ class _BMIWidgetState extends State { spacing: 8.h, children: [ Radio( - value: unit, + value: value, groupValue: selectedHeightUnit, activeColor: AppColors.errorColor, onChanged: (String? value) { @@ -152,11 +173,12 @@ class _BMIWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + // show localized label (if available) otherwise show the key itself + key.tr(context: context).toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { - selectedHeightUnit = unit; + selectedHeightUnit = value; }); Navigator.pop(context); }), @@ -187,7 +209,7 @@ class _BMIWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Weight".toText12(isBold: true, color: AppColors.inputLabelTextColor), + LocaleKeys.weight.tr().toText12(isBold: true, color: AppColors.inputLabelTextColor), Container( height: 20.w, alignment: Alignment.centerLeft, @@ -227,8 +249,9 @@ class _BMIWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor), - selectedWeightUnit.toUpperCase().toText12(isBold: true, color: AppColors.textColor), + LocaleKeys.unit.tr().toText12(isBold: true, color: AppColors.inputLabelTextColor), + // Show localized label for the currently selected weight unit + _getUnitLabel(_weightUnitsMap, selectedWeightUnit).toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), @@ -243,9 +266,11 @@ class _BMIWidgetState extends State { decoration: BoxDecoration(color: AppColors.whiteColor, borderRadius: BorderRadius.circular(20.r)), child: ListView.separated( shrinkWrap: true, - itemCount: _weightUnits.length, + itemCount: _weightUnitsMap.length, itemBuilder: (context, index) { - final unit = _weightUnits[index]; + final entry = _weightUnitsMap.entries.elementAt(index); + final key = entry.key; + final value = entry.value; return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, @@ -256,7 +281,7 @@ class _BMIWidgetState extends State { spacing: 8.h, children: [ Radio( - value: unit, + value: value, groupValue: selectedWeightUnit, activeColor: AppColors.errorColor, onChanged: (String? value) { @@ -268,11 +293,11 @@ class _BMIWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + key.tr(context: context).toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { - selectedWeightUnit = unit; + selectedWeightUnit = value; _onInputChanged(); }); Navigator.pop(context); diff --git a/lib/presentation/health_calculators_and_converts/widgets/bmr.dart b/lib/presentation/health_calculators_and_converts/widgets/bmr.dart index 21a5df61..2b013549 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/bmr.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/bmr.dart @@ -26,8 +26,31 @@ class _BMRWidgetState extends State { final TextEditingController _heightController = TextEditingController(); final TextEditingController _weightController = TextEditingController(); final TextEditingController _ageController = TextEditingController(); - final List _heightUnits = ['cm', 'm', 'ft', 'in']; - final List _weightUnits = ['kg', 'lb']; + + final Map _heightUnits = { + 'cm': LocaleKeys.cm, + 'm': LocaleKeys.m, + 'ft': LocaleKeys.ft, + 'in': LocaleKeys.inch, + }; + + final Map _weightUnits = { + 'kg': LocaleKeys.kg, + 'lb': LocaleKeys.lb, + }; + + final Map _genders = { + 'Male': LocaleKeys.malE, + 'Female': LocaleKeys.femaleGender, + }; + + final Map _activityLevels = { + 'Almost Inactive (no exercise)': LocaleKeys.almostInactive, + 'Lightly active': LocaleKeys.lightlyActive, + 'Lightly active (1-3) days per week': LocaleKeys.lightlyActive13Days, + 'Super active (very hard exercise)': LocaleKeys.superActive, + }; + String selectedHeightUnit = 'cm'; String selectedWeightUnit = 'kg'; String selectedActivityLevel = 'Lightly active'; @@ -90,7 +113,7 @@ class _BMRWidgetState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ LocaleKeys.selectGender.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), - selectedGender.toCamelCase.toText12(isBold: true, color: AppColors.textColor), + (_genders[selectedGender] ?? selectedGender).tr().toText12(isBold: true, color: AppColors.textColor), ], ), ], @@ -100,8 +123,6 @@ class _BMRWidgetState extends State { SizedBox(width: 4.w), ], ).paddingSymmetrical(0.w, 16.w).onPress(() { - List _genders = ["Male", "Female"]; - dialogService.showFamilyBottomSheetWithoutHWithChild( label: LocaleKeys.selectGender.tr(context: context), message: "", @@ -112,7 +133,8 @@ class _BMRWidgetState extends State { shrinkWrap: true, itemCount: _genders.length, itemBuilder: (context, index) { - final unit = _genders[index]; + final key = _genders.keys.elementAt(index); + final localeKey = _genders[key]!; return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, @@ -123,12 +145,11 @@ class _BMRWidgetState extends State { spacing: 8.h, children: [ Radio( - value: unit, + value: key, groupValue: selectedGender, activeColor: AppColors.errorColor, onChanged: (String? value) { if (value == null) return; - print(value); setState(() { selectedGender = value; }); @@ -137,11 +158,11 @@ class _BMRWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toCamelCase.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + localeKey.tr().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { - selectedGender = unit; + selectedGender = key; }); _onInputChanged(); Navigator.pop(context); @@ -243,7 +264,7 @@ class _BMRWidgetState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ LocaleKeys.unit.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), - selectedHeightUnit.toText12(isBold: true, color: AppColors.textColor), + (_heightUnits[selectedHeightUnit] ?? selectedHeightUnit).tr().toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), @@ -260,7 +281,8 @@ class _BMRWidgetState extends State { shrinkWrap: true, itemCount: _heightUnits.length, itemBuilder: (context, index) { - final unit = _heightUnits[index]; + final key = _heightUnits.keys.elementAt(index); + final localeKey = _heightUnits[key]!; return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, @@ -271,7 +293,7 @@ class _BMRWidgetState extends State { spacing: 8.h, children: [ Radio( - value: unit, + value: key, groupValue: selectedHeightUnit, activeColor: AppColors.errorColor, onChanged: (String? value) { @@ -284,13 +306,13 @@ class _BMRWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + localeKey.tr().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { - selectedHeightUnit = unit; - _onInputChanged(); + selectedHeightUnit = key; }); + _onInputChanged(); Navigator.pop(context); }), ), @@ -361,7 +383,7 @@ class _BMRWidgetState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ "Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor), - selectedWeightUnit.toText12(isBold: true, color: AppColors.textColor), + (_weightUnits[selectedWeightUnit] ?? selectedWeightUnit).tr().toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), @@ -378,7 +400,8 @@ class _BMRWidgetState extends State { shrinkWrap: true, itemCount: _weightUnits.length, itemBuilder: (context, index) { - final unit = _weightUnits[index]; + final key = _weightUnits.keys.elementAt(index); + final localeKey = _weightUnits[key]!; return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, @@ -389,7 +412,7 @@ class _BMRWidgetState extends State { spacing: 8.h, children: [ Radio( - value: unit, + value: key, groupValue: selectedWeightUnit, activeColor: AppColors.errorColor, onChanged: (String? value) { @@ -402,13 +425,13 @@ class _BMRWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + localeKey.tr().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { - selectedWeightUnit = unit; - _onInputChanged(); + selectedWeightUnit = key; }); + _onInputChanged(); Navigator.pop(context); }), ), @@ -440,7 +463,7 @@ class _BMRWidgetState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ LocaleKeys.activityLevel.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), - selectedActivityLevel.toText12(isBold: true, color: AppColors.textColor), + (_activityLevels[selectedActivityLevel] ?? selectedActivityLevel).tr().toText12(isBold: true, color: AppColors.textColor), ], ), ], @@ -450,7 +473,6 @@ class _BMRWidgetState extends State { SizedBox(width: 4.w), ], ).paddingSymmetrical(0.w, 16.w).onPress(() { - List _activity = ["Almost Inactive (no exercise)", "Lightly active", "Lightly active (1-3) days per week", "Super active (very hard exercise)"]; dialogService.showFamilyBottomSheetWithoutHWithChild( label: LocaleKeys.selectActivityLevel.tr(context: context), message: "", @@ -459,9 +481,10 @@ class _BMRWidgetState extends State { decoration: BoxDecoration(color: AppColors.whiteColor, borderRadius: BorderRadius.circular(20.r)), child: ListView.separated( shrinkWrap: true, - itemCount: _activity.length, + itemCount: _activityLevels.length, itemBuilder: (context, index) { - final unit = _activity[index]; + final key = _activityLevels.keys.elementAt(index); + final localeKey = _activityLevels[key]!; return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, @@ -472,7 +495,7 @@ class _BMRWidgetState extends State { spacing: 8.h, children: [ Radio( - value: unit, + value: key, groupValue: selectedActivityLevel, activeColor: AppColors.errorColor, onChanged: (String? value) { @@ -485,13 +508,13 @@ class _BMRWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toCamelCase.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + localeKey.tr().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { - selectedActivityLevel = unit; - _onInputChanged(); + selectedActivityLevel = key; }); + _onInputChanged(); Navigator.pop(context); }), ), @@ -513,7 +536,7 @@ class _BMRWidgetState extends State { Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w), SizedBox(width: 12.w), Expanded( - child: "Calculates daily calorie intake based on server factors, like height, weight, age, gender and daily physical activity." + child: LocaleKeys.calculateCaloriesBasedOnFactors.tr() .toText12(isBold: true, color: AppColors.inputLabelTextColor), ), ], diff --git a/lib/presentation/health_calculators_and_converts/widgets/calories.dart b/lib/presentation/health_calculators_and_converts/widgets/calories.dart index 8e584e96..ad31cd6c 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/calories.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/calories.dart @@ -26,8 +26,31 @@ class _CaloriesWidgetState extends State { final TextEditingController _heightController = TextEditingController(); final TextEditingController _weightController = TextEditingController(); final TextEditingController _ageController = TextEditingController(); - final List _heightUnits = ['cm', 'm', 'ft', 'in']; - final List _weightUnits = ['kg', 'lb']; + + final Map _heightUnits = { + 'cm': LocaleKeys.cm, + 'm': LocaleKeys.m, + 'ft': LocaleKeys.ft, + 'in': LocaleKeys.inch, + }; + + final Map _weightUnits = { + 'kg': LocaleKeys.kg, + 'lb': LocaleKeys.lb, + }; + + final Map _genders = { + 'Male': LocaleKeys.malE, + 'Female': LocaleKeys.femaleGender, + }; + + final Map _activityLevels = { + 'Almost Inactive (no exercise)': LocaleKeys.almostInactive, + 'Lightly active': LocaleKeys.lightlyActive, + 'Lightly active (1-3) days per week': LocaleKeys.lightlyActive13Days, + 'Super active (very hard exercise)': LocaleKeys.superActive, + }; + String selectedHeightUnit = 'cm'; String selectedWeightUnit = 'kg'; String selectedActivityLevel = 'Lightly active'; @@ -69,7 +92,6 @@ class _CaloriesWidgetState extends State { @override Widget build(BuildContext context) { DialogService dialogService = getIt(); - AppState appState = getIt(); return Consumer(builder: (context, provider, _) { return Container( @@ -78,6 +100,7 @@ class _CaloriesWidgetState extends State { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), child: Column( children: [ + // === GENDER ROW === Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -90,8 +113,8 @@ class _CaloriesWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Select Gender".toText12(isBold: true, color: AppColors.inputLabelTextColor), - selectedGender.toCamelCase.toText12(isBold: true, color: AppColors.textColor), + LocaleKeys.selectGender.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), + (_genders[selectedGender] ?? selectedGender).tr().toText12(isBold: true, color: AppColors.textColor), ], ), ], @@ -101,8 +124,6 @@ class _CaloriesWidgetState extends State { SizedBox(width: 4.w), ], ).paddingSymmetrical(0.w, 16.w).onPress(() { - List _genders = ["Male", "Female"]; - dialogService.showFamilyBottomSheetWithoutHWithChild( label: LocaleKeys.selectGender.tr(context: context), message: "", @@ -113,7 +134,8 @@ class _CaloriesWidgetState extends State { shrinkWrap: true, itemCount: _genders.length, itemBuilder: (context, index) { - final unit = _genders[index]; + final key = _genders.keys.elementAt(index); + final localeKey = _genders[key]!; return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, @@ -124,7 +146,7 @@ class _CaloriesWidgetState extends State { spacing: 8.h, children: [ Radio( - value: unit, + value: key, groupValue: selectedGender, activeColor: AppColors.errorColor, onChanged: (String? value) { @@ -137,13 +159,13 @@ class _CaloriesWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toCamelCase.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + localeKey.tr().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { - selectedGender = unit; - _onInputChanged(); + selectedGender = key; }); + _onInputChanged(); Navigator.pop(context); }), ), @@ -159,6 +181,7 @@ class _CaloriesWidgetState extends State { ); }), Divider(height: 1, color: Color(0xFFEEEEEE)), + // === AGE ROW (unchanged) === Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -196,6 +219,7 @@ class _CaloriesWidgetState extends State { ], ).paddingSymmetrical(0.w, 16.w), Divider(height: 1, color: Color(0xFFEEEEEE)), + // === HEIGHT ROW === Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -242,72 +266,24 @@ class _CaloriesWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor), - selectedHeightUnit.toText12(isBold: true, color: AppColors.textColor), + LocaleKeys.unit.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), + (_heightUnits[selectedHeightUnit] ?? selectedHeightUnit).tr().toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), Utils.buildSvgWithAssets(icon: AppAssets.arrow_down) ], ).onPress(() { - dialogService.showFamilyBottomSheetWithoutHWithChild( - label: LocaleKeys.unit.tr(context: context), - message: "", - child: Container( - padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), - decoration: BoxDecoration(color: AppColors.whiteColor, borderRadius: BorderRadius.circular(20.r)), - child: ListView.separated( - shrinkWrap: true, - itemCount: _heightUnits.length, - itemBuilder: (context, index) { - final unit = _heightUnits[index]; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - SizedBox( - height: 70, - child: Row( - spacing: 8.h, - children: [ - Radio( - value: unit, - groupValue: selectedHeightUnit, - activeColor: AppColors.errorColor, - onChanged: (String? value) { - if (value == null) return; - setState(() { - selectedHeightUnit = value; - }); - Navigator.pop(context); - }, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, - ], - ).onPress(() { - setState(() { - selectedHeightUnit = unit; - _onInputChanged(); - }); - Navigator.pop(context); - }), - ), - ], - ); - }, - separatorBuilder: (BuildContext context, int index) { - return Divider(height: 1, color: Color(0xFFEEEEEE)); - }, - ), - ), - onOkPressed: () {}, - ); + _showUnitBottomSheet(context, dialogService, _heightUnits, selectedHeightUnit, (value) { + setState(() { selectedHeightUnit = value; }); + _onInputChanged(); + }); }), ), ], ).paddingSymmetrical(0.w, 16.w), Divider(height: 1, color: Color(0xFFEEEEEE)), + // === WEIGHT ROW === Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -359,73 +335,24 @@ class _CaloriesWidgetState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor), - selectedWeightUnit.toText12(isBold: true, color: AppColors.textColor), + LocaleKeys.unit.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), + (_weightUnits[selectedWeightUnit] ?? selectedWeightUnit).tr().toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), Utils.buildSvgWithAssets(icon: AppAssets.arrow_down) ], ).onPress(() { - dialogService.showFamilyBottomSheetWithoutHWithChild( - label: LocaleKeys.unit.tr(context: context), - message: "", - child: Container( - padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), - decoration: BoxDecoration(color: AppColors.whiteColor, borderRadius: BorderRadius.circular(20.r)), - child: ListView.separated( - shrinkWrap: true, - itemCount: _weightUnits.length, - itemBuilder: (context, index) { - final unit = _weightUnits[index]; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - SizedBox( - height: 70, - child: Row( - spacing: 8.h, - children: [ - Radio( - value: unit, - groupValue: selectedWeightUnit, - activeColor: AppColors.errorColor, - onChanged: (String? value) { - if (value == null) return; - setState(() { - selectedWeightUnit = value; - }); - _onInputChanged(); - Navigator.pop(context); - }, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, - ], - ).onPress(() { - setState(() { - selectedWeightUnit = unit; - _onInputChanged(); - }); - Navigator.pop(context); - }), - ), - ], - ); - }, - separatorBuilder: (BuildContext context, int index) { - return Divider(height: 1, color: Color(0xFFEEEEEE)); - }, - ), - ), - onOkPressed: () {}, - ); + _showUnitBottomSheet(context, dialogService, _weightUnits, selectedWeightUnit, (value) { + setState(() { selectedWeightUnit = value; }); + _onInputChanged(); + }); }), ), ], ).paddingSymmetrical(0.w, 16.w), Divider(height: 1, color: Color(0xFFEEEEEE)), + // === ACTIVITY LEVEL ROW === Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -439,7 +366,7 @@ class _CaloriesWidgetState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ LocaleKeys.activityLevel.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), - selectedActivityLevel.toText12(isBold: true, color: AppColors.textColor), + (_activityLevels[selectedActivityLevel] ?? selectedActivityLevel).tr().toText12(isBold: true, color: AppColors.textColor), ], ), ], @@ -449,7 +376,6 @@ class _CaloriesWidgetState extends State { SizedBox(width: 4.w), ], ).paddingSymmetrical(0.w, 16.w).onPress(() { - List _activity = ["Almost Inactive (no exercise)", "Lightly active", "Lightly active (1-3) days per week", "Super active (very hard exercise)"]; dialogService.showFamilyBottomSheetWithoutHWithChild( label: LocaleKeys.selectActivityLevel.tr(context: context), message: "", @@ -458,9 +384,10 @@ class _CaloriesWidgetState extends State { decoration: BoxDecoration(color: AppColors.whiteColor, borderRadius: BorderRadius.circular(20.r)), child: ListView.separated( shrinkWrap: true, - itemCount: _activity.length, + itemCount: _activityLevels.length, itemBuilder: (context, index) { - final unit = _activity[index]; + final key = _activityLevels.keys.elementAt(index); + final localeKey = _activityLevels[key]!; return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, @@ -471,7 +398,7 @@ class _CaloriesWidgetState extends State { spacing: 8.h, children: [ Radio( - value: unit, + value: key, groupValue: selectedActivityLevel, activeColor: AppColors.errorColor, onChanged: (String? value) { @@ -484,13 +411,13 @@ class _CaloriesWidgetState extends State { }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toCamelCase.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + localeKey.tr().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { - selectedActivityLevel = unit; - _onInputChanged(); + selectedActivityLevel = key; }); + _onInputChanged(); Navigator.pop(context); }), ), @@ -512,7 +439,7 @@ class _CaloriesWidgetState extends State { Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w), SizedBox(width: 12.w), Expanded( - child: "Calculates daily calorie intake based on server factors, like height, weight, age, gender and daily physical activity." + child: LocaleKeys.calculateCaloriesBasedOnFactors.tr() .toText12(isBold: true, color: AppColors.inputLabelTextColor), ), ], @@ -522,4 +449,56 @@ class _CaloriesWidgetState extends State { ); }); } + + void _showUnitBottomSheet(BuildContext context, DialogService dialogService, Map units, String currentValue, Function(String) onSelected) { + dialogService.showFamilyBottomSheetWithoutHWithChild( + label: LocaleKeys.unit.tr(context: context), + message: "", + child: Container( + padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 4.h, bottom: 4.h), + decoration: BoxDecoration(color: AppColors.whiteColor, borderRadius: BorderRadius.circular(20.r)), + child: ListView.separated( + shrinkWrap: true, + itemCount: units.length, + itemBuilder: (context, index) { + final key = units.keys.elementAt(index); + final localeKey = units[key]!; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox( + height: 70, + child: Row( + spacing: 8.h, + children: [ + Radio( + value: key, + groupValue: currentValue, + activeColor: AppColors.errorColor, + onChanged: (String? value) { + if (value == null) return; + onSelected(value); + Navigator.pop(context); + }, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + localeKey.tr().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + ], + ).onPress(() { + onSelected(key); + Navigator.pop(context); + }), + ), + ], + ); + }, + separatorBuilder: (BuildContext context, int index) { + return Divider(height: 1, color: Color(0xFFEEEEEE)); + }, + ), + ), + onOkPressed: () {}, + ); + } } diff --git a/lib/presentation/health_calculators_and_converts/widgets/crabs.dart b/lib/presentation/health_calculators_and_converts/widgets/crabs.dart index e3e7c1e3..8a2f6343 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/crabs.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/crabs.dart @@ -187,7 +187,7 @@ class _CrabsWidgetState extends State { Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w), SizedBox(width: 12.w), Expanded( - child: "Calculate carbohydrate based protein and fat ration in calories and grams according to a pre set ratio." + child: LocaleKeys.carbsAlert.tr() .toText12(isBold: true, color: AppColors.inputLabelTextColor), ), ], diff --git a/lib/presentation/health_calculators_and_converts/widgets/health_card.dart b/lib/presentation/health_calculators_and_converts/widgets/health_card.dart index 21482e7c..e6c3d4d4 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/health_card.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/health_card.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -9,41 +12,55 @@ class HealthCard extends StatelessWidget { const HealthCard({ super.key, required this.icon, + this.iconColor, + this.iconBgColor, required this.labelText, + required this.subtitle, this.onTap, }); final String icon; + final Color? iconColor; + final Color? iconBgColor; final String labelText; + final String subtitle; final VoidCallback? onTap; @override Widget build(BuildContext context) { + AppState appState = getIt.get(); return Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.r, - hasShadow: false, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r, hasShadow: false), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, children: [ - Utils.buildSvgWithAssets( - icon: icon, + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: iconBgColor ?? Colors.transparent, borderRadius: 10.r), + height: 40.w, width: 40.w, - height: 40.h, - fit: BoxFit.cover, - ).toShimmer2(isShow: false, radius: 12.r), - SizedBox(height: 14.h), - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Flexible( - child: labelText.toText12( - isBold: true, - maxLine: 2, - )), - ], + child: Utils.buildSvgWithAssets(icon: icon, fit: BoxFit.none, height: 22.w, width: 22.w, iconColor: iconColor), + ), + SizedBox(width: 12.h), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + labelText.toText16(weight: FontWeight.w600), + ], + ), + SizedBox(height: 4.h), + subtitle.toText12(maxLine: 2, color: AppColors.greyTextColor), + ], + ), + ), + SizedBox(width: 2.h), + Transform.flip( + flipX: appState.getLanguageCode() == "ar", + child: Utils.buildSvgWithAssets(icon: AppAssets.arrowRight, width: 24.w, height: 24.h, fit: BoxFit.cover, iconColor: AppColors.textColor), ), ], ).paddingAll(16.w).onPress(() { diff --git a/lib/presentation/health_calculators_and_converts/widgets/ibw.dart b/lib/presentation/health_calculators_and_converts/widgets/ibw.dart index 070e373e..f9d7e964 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/ibw.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/ibw.dart @@ -24,12 +24,24 @@ class IdealBodyWeightWidget extends StatefulWidget { class _IdealBodyWeightWidgetState extends State { final TextEditingController _heightController = TextEditingController(); final TextEditingController _weightController = TextEditingController(); - final List _heightUnits = ['cm', 'm', 'ft', 'in']; - final List _weightUnits = ['kg', 'lb']; + final Map _heightUnits = { + 'cm': LocaleKeys.cm, + 'm': LocaleKeys.m, + 'ft': LocaleKeys.ft, + 'in': LocaleKeys.inch, + }; + final Map _weightUnits = { + 'kg': LocaleKeys.kg, + 'lb': LocaleKeys.lb, + }; + final Map _bodyFrameSizes = { + 'Small (fingers overlaps)': LocaleKeys.bodyFrameSmall, + 'Medium (fingers touch)': LocaleKeys.bodyFrameMedium, + "Large (fingers don't touch)": LocaleKeys.bodyFrameLarge, + }; String selectedHeightUnit = 'cm'; String selectedWeightUnit = 'kg'; String selectedBodyFrameSize = 'Medium (fingers touch)'; - Function(String)? onUnitSelected; @override void initState() { @@ -117,7 +129,7 @@ class _IdealBodyWeightWidgetState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ LocaleKeys.unit.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), - selectedHeightUnit.toText12(isBold: true, color: AppColors.textColor), + (_heightUnits[selectedHeightUnit] ?? selectedHeightUnit).tr().toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), @@ -134,7 +146,8 @@ class _IdealBodyWeightWidgetState extends State { shrinkWrap: true, itemCount: _heightUnits.length, itemBuilder: (context, index) { - final unit = _heightUnits[index]; + final key = _heightUnits.keys.elementAt(index); + final localeKey = _heightUnits[key]!; return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, @@ -145,7 +158,7 @@ class _IdealBodyWeightWidgetState extends State { spacing: 8.h, children: [ Radio( - value: unit, + value: key, groupValue: selectedHeightUnit, activeColor: AppColors.errorColor, onChanged: (String? value) { @@ -153,17 +166,18 @@ class _IdealBodyWeightWidgetState extends State { setState(() { selectedHeightUnit = value; }); + _onInputChanged(); Navigator.pop(context); }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + localeKey.tr().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { - selectedHeightUnit = unit; - _onInputChanged(); + selectedHeightUnit = key; }); + _onInputChanged(); Navigator.pop(context); }), ), @@ -234,7 +248,7 @@ class _IdealBodyWeightWidgetState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ LocaleKeys.unit.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), - selectedWeightUnit.toText12(isBold: true, color: AppColors.textColor), + (_weightUnits[selectedWeightUnit] ?? selectedWeightUnit).tr().toText12(isBold: true, color: AppColors.textColor), ], ), SizedBox(width: 12.w), @@ -251,7 +265,8 @@ class _IdealBodyWeightWidgetState extends State { shrinkWrap: true, itemCount: _weightUnits.length, itemBuilder: (context, index) { - final unit = _weightUnits[index]; + final key = _weightUnits.keys.elementAt(index); + final localeKey = _weightUnits[key]!; return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, @@ -262,7 +277,7 @@ class _IdealBodyWeightWidgetState extends State { spacing: 8.h, children: [ Radio( - value: unit, + value: key, groupValue: selectedWeightUnit, activeColor: AppColors.errorColor, onChanged: (String? value) { @@ -270,16 +285,18 @@ class _IdealBodyWeightWidgetState extends State { setState(() { selectedWeightUnit = value; }); + _onInputChanged(); + Navigator.pop(context); }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toUpperCase().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + localeKey.tr().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { - selectedWeightUnit = unit; - _onInputChanged(); + selectedWeightUnit = key; }); + _onInputChanged(); Navigator.pop(context); }), ), @@ -311,7 +328,7 @@ class _IdealBodyWeightWidgetState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ LocaleKeys.bodyFrameSize.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), - selectedBodyFrameSize.toText12(isBold: true, color: AppColors.textColor), + (_bodyFrameSizes[selectedBodyFrameSize] ?? selectedBodyFrameSize).tr().toText12(isBold: true, color: AppColors.textColor), ], ), ], @@ -321,7 +338,6 @@ class _IdealBodyWeightWidgetState extends State { SizedBox(width: 4.w), ], ).paddingSymmetrical(0.w, 16.w).onPress(() { - List _activity = ["Small (fingers overlaps)", "Medium (fingers touch)", "Large (fingers don't touch)"]; dialogService.showFamilyBottomSheetWithoutHWithChild( label: LocaleKeys.selectBodyFrameSize.tr(context: context), message: "", @@ -330,9 +346,10 @@ class _IdealBodyWeightWidgetState extends State { decoration: BoxDecoration(color: AppColors.whiteColor, borderRadius: BorderRadius.circular(20.r)), child: ListView.separated( shrinkWrap: true, - itemCount: _activity.length, + itemCount: _bodyFrameSizes.length, itemBuilder: (context, index) { - final unit = _activity[index]; + final key = _bodyFrameSizes.keys.elementAt(index); + final localeKey = _bodyFrameSizes[key]!; return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, @@ -343,7 +360,7 @@ class _IdealBodyWeightWidgetState extends State { spacing: 8.h, children: [ Radio( - value: unit, + value: key, groupValue: selectedBodyFrameSize, activeColor: AppColors.errorColor, onChanged: (String? value) { @@ -351,17 +368,18 @@ class _IdealBodyWeightWidgetState extends State { setState(() { selectedBodyFrameSize = value; }); + _onInputChanged(); Navigator.pop(context); }, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - unit.toCamelCase.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, + localeKey.tr().toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(() { setState(() { - selectedBodyFrameSize = unit; - _onInputChanged(); + selectedBodyFrameSize = key; }); + _onInputChanged(); Navigator.pop(context); }), ), @@ -383,7 +401,7 @@ class _IdealBodyWeightWidgetState extends State { Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w), SizedBox(width: 12.w), Expanded( - child: "Calculates the ideal body weight based on height, weight and body size.".toText12(isBold: true, color: AppColors.inputLabelTextColor), + child: LocaleKeys.ibwAlert.tr().toText12(isBold: true, color: AppColors.inputLabelTextColor), ), ], ).paddingSymmetrical(0.w, 16.w), diff --git a/lib/presentation/health_calculators_and_converts/widgets/ovulation.dart b/lib/presentation/health_calculators_and_converts/widgets/ovulation.dart index 3cfd72f8..1769f0bc 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/ovulation.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/ovulation.dart @@ -1,5 +1,7 @@ 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/generated/locale_keys.g.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -56,6 +58,7 @@ class _OvulationWidgetState extends State { @override Widget build(BuildContext context) { + AppState appState = getIt(); return Consumer(builder: (context, provider, _) { return Container( margin: EdgeInsets.zero, @@ -165,7 +168,9 @@ class _OvulationWidgetState extends State { Utils.buildSvgWithAssets(icon: AppAssets.globe, width: 18.w, height: 18.w), SizedBox(width: 12.w), Expanded( - child: "Calculate ovulation and fertile window based on last period, cycle length and luteal phase.".toText12(isBold: true, color: AppColors.inputLabelTextColor), + child: appState.isArabic() + ? "احسب فترة الإباضة وفترة الخصوبة بناءً على آخر دورة شهرية وطول الدورة الشهرية ومرحلة الجسم الأصفر.".toText12(isBold: true, color: AppColors.inputLabelTextColor) + : "Calculate ovulation and fertile window based on last period, cycle length and luteal phase.".toText12(isBold: true, color: AppColors.inputLabelTextColor), ), ], ).paddingSymmetrical(0.w, 16.w), diff --git a/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart b/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart index b61eaaed..dd5eb0f0 100644 --- a/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart +++ b/lib/presentation/health_calculators_and_converts/widgets/triglycerides.dart @@ -1,4 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; @@ -80,7 +82,7 @@ class _TriglyceridesWidgetState extends State { child: Column( children: [ _buildInputField( - label: "MG/DL", + label: LocaleKeys.mldl.tr(), hint: "150", controller: _mgdlController, focusNode: _mgdlFocus, @@ -111,7 +113,7 @@ class _TriglyceridesWidgetState extends State { ), _buildInputField( - label: "MMOL/L", + label: LocaleKeys.mmol, hint: "1.7", controller: _mmolController, focusNode: _mmolFocus, @@ -134,7 +136,7 @@ class _TriglyceridesWidgetState extends State { SizedBox(width: 12.w), Expanded( child: - "Convert triglyceride values between mg/dL and mmol/L." + LocaleKeys.triglycerideInfo.tr() .toText12( isBold: true, color: AppColors.inputLabelTextColor, diff --git a/lib/presentation/health_trackers/add_health_tracker_entry_page.dart b/lib/presentation/health_trackers/add_health_tracker_entry_page.dart index 2d0356c3..9f7f01cf 100644 --- a/lib/presentation/health_trackers/add_health_tracker_entry_page.dart +++ b/lib/presentation/health_trackers/add_health_tracker_entry_page.dart @@ -154,17 +154,20 @@ class _AddHealthTrackerEntryPageState extends State { } // Show success message and pop back - void _showSuccessAndPop() { - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getSuccessWidget( - loadingText: _getSuccessMessage(), + + void _showSuccessAndPop() async { + showCommonBottomSheetWithoutHeight(context, + child: Utils.getSuccessWidget( + loadingText: _getSuccessMessage(), ).paddingSymmetrical(0.h, 24.h), callBackFunc: () { // Navigator.pop(context); - }, - isCloseButtonVisible: false, - isDismissible: true, - isFullScreen: false, isAutoDismiss: true); + }, isCloseButtonVisible: false, isDismissible: true, isFullScreen: false, isAutoDismiss: false); + await Future.delayed(Duration(milliseconds: 2000)).then((value) { + if (mounted) { + Navigator.pop(context); + Navigator.pop(context); + } + }); } // Reusable method to build selection row widget @@ -186,9 +189,7 @@ class _AddHealthTrackerEntryPageState extends State { onChanged: (_) => onTap(), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - (useUpperCase ? value.toUpperCase() : value.toCamelCase) - .toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1) - .expanded, + (useUpperCase ? value.toUpperCase() : value.toCamelCase).toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(onTap), ); @@ -231,7 +232,7 @@ class _AddHealthTrackerEntryPageState extends State { onOkPressed: () {}, ); } - + // Blood Sugar unit selection void _showBloodSugarUnitSelectionBottomSheet(BuildContext context, HealthTrackersViewModel viewModel) { FocusScope.of(context).unfocus(); @@ -251,7 +252,7 @@ class _AddHealthTrackerEntryPageState extends State { _showSelectionBottomSheet( context: context, title: LocaleKeys.selectMeasureTime.tr(context: context), - items: viewModel.bloodSugarMeasureTimeEnList, + items: viewModel.getBloodSugarTimeList(), selectedValue: viewModel.selectedBloodSugarMeasureTime, onSelected: viewModel.setBloodSugarMeasureTime, useUpperCase: false, @@ -264,8 +265,8 @@ class _AddHealthTrackerEntryPageState extends State { _showSelectionBottomSheet( context: context, title: LocaleKeys.selectUnit.tr(context: context), - items: viewModel.weightUnits, - selectedValue: viewModel.selectedWeightUnit, + items: viewModel.getWeightUnitsList(), + selectedValue: viewModel.selectedWeightUnitDisplay, onSelected: viewModel.setWeightUnit, useUpperCase: false, ); @@ -277,8 +278,8 @@ class _AddHealthTrackerEntryPageState extends State { _showSelectionBottomSheet( context: context, title: LocaleKeys.selectArm.tr(context: context), - items: viewModel.measuredArmList, - selectedValue: viewModel.selectedMeasuredArm, + items: viewModel.getMeasuredArmList(), + selectedValue: viewModel.selectedMeasuredArmDisplay, onSelected: viewModel.setMeasuredArm, useUpperCase: false, ); @@ -366,7 +367,7 @@ class _AddHealthTrackerEntryPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor), + LocaleKeys.unit.tr(context: context).toText12(isBold: true, color: AppColors.inputLabelTextColor), unit.toText12(isBold: true, color: AppColors.textColor), ], ), @@ -441,7 +442,7 @@ class _AddHealthTrackerEntryPageState extends State { icon: AppAssets.bodyIcon, iconColor: AppColors.greyTextColor, label: LocaleKeys.selectArm.tr(context: context), - value: viewModel.selectedMeasuredArm, + value: viewModel.selectedMeasuredArmDisplay, onRowTap: () => _showMeasuredArmSelectionBottomSheet(context, viewModel), ), _buildDateTimeFields(), @@ -457,7 +458,7 @@ class _AddHealthTrackerEntryPageState extends State { icon: AppAssets.weightScale, label: LocaleKeys.enterWeight.tr(context: context), inputField: _buildTextField(viewModel.weightController, '', keyboardType: TextInputType.number), - unit: viewModel.selectedWeightUnit, + unit: viewModel.selectedWeightUnitDisplay, onUnitTap: () => _showWeightUnitSelectionBottomSheet(context, viewModel), ), _buildDateTimeFields(), @@ -469,11 +470,12 @@ class _AddHealthTrackerEntryPageState extends State { Widget _buildDateTimeFields() { return Column( children: [ + SizedBox(width: 8.w), TextInputWidget( controller: dateController, isReadOnly: true, isArrowTrailing: true, - labelText: "Date", + labelText: LocaleKeys.date.tr(context: context), hintText: LocaleKeys.pickADate.tr(context: context), focusNode: FocusNode(), isEnable: true, @@ -485,6 +487,7 @@ class _AddHealthTrackerEntryPageState extends State { leadingIcon: AppAssets.calendarGrey, selectionType: SelectionTypeEnum.calendar, isHideSwitcher: true, + btnTitle: LocaleKeys.add.tr(context: context), onCalendarTypeChanged: (val) {}, onChange: (val) { if (val == null) return; @@ -499,11 +502,13 @@ class _AddHealthTrackerEntryPageState extends State { } }, ), + Divider(height: 1, color: AppColors.dividerColor), + SizedBox(width: 8.w), TextInputWidget( controller: timeController, isReadOnly: true, isArrowTrailing: true, - labelText: "Time", + labelText: LocaleKeys.time.tr(context: context), hintText: LocaleKeys.selectMeasureTime.tr(context: context), focusNode: FocusNode(), isEnable: true, @@ -539,11 +544,7 @@ class _AddHealthTrackerEntryPageState extends State { body: CollapsingListView( title: _getPageTitle(), bottomChild: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.r, - hasShadow: true, - ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), child: Padding( padding: EdgeInsets.all(24.w), child: CustomButton( diff --git a/lib/presentation/health_trackers/health_tracker_detail_page.dart b/lib/presentation/health_trackers/health_tracker_detail_page.dart index 737a150f..35956588 100644 --- a/lib/presentation/health_trackers/health_tracker_detail_page.dart +++ b/lib/presentation/health_trackers/health_tracker_detail_page.dart @@ -27,6 +27,7 @@ import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/graph/custom_graph.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:open_filex/open_filex.dart'; import 'package:provider/provider.dart'; import 'package:shimmer/shimmer.dart'; @@ -91,13 +92,15 @@ class _HealthTrackerDetailPageState extends State { /// Get empty state message based on tracker type String _getEmptyStateMessage() { + final isArabic = getIt.get().isArabic(); + switch (widget.trackerType) { case HealthTrackerTypeEnum.bloodSugar: - return "Please add data to track your Blood Sugar"; + return isArabic ? "الرجاء إضافة قراءة جديدة لتتبع سكر الدم" : "Please add data to track your Blood Sugar"; case HealthTrackerTypeEnum.bloodPressure: - return "Please add data to track your Blood Pressure"; + return isArabic ? "الرجاء إضافة قراءة جديدة لتتبع ضغط الدم" : "Please add data to track your Blood Pressure"; case HealthTrackerTypeEnum.weightTracker: - return "Please add data to track your Weight"; + return isArabic ? "الرجاء إضافة قراءة جديدة لتتبع الوزن" : "Please add data to track your Weight"; } } @@ -187,7 +190,7 @@ class _HealthTrackerDetailPageState extends State { context: context, title: LocaleKeys.selectDuration.tr(), items: viewModel.durationFilters, - selectedValue: viewModel.selectedDurationFilter, + selectedValue: viewModel.selectedDurationFilterDisplay, onSelected: viewModel.setFilterDuration, ); } @@ -303,7 +306,7 @@ class _HealthTrackerDetailPageState extends State { child: Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - viewModel.selectedDurationFilter.toText12(isBold: true), + viewModel.selectedDurationFilterDisplay.toText12(isBold: true), SizedBox(width: 4.w), Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, height: 16.h), ], @@ -572,6 +575,8 @@ class _HealthTrackerDetailPageState extends State { } Widget _buildEmptyStateWidget() { + final isArabic = getIt.get().isArabic(); + return SizedBox( height: MediaQuery.of(context).size.height * 0.5, child: Center( @@ -586,7 +591,7 @@ class _HealthTrackerDetailPageState extends State { width: 48.w, ), SizedBox(height: 16.h), - "You do not have any data available.".toText14( + (isArabic ? "ليس لديك أي بيانات متاحة." : "You do not have any data available.").toText14( weight: FontWeight.w600, color: AppColors.textColor, isCenter: true, @@ -1250,7 +1255,32 @@ class _HealthTrackerDetailPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - sendEmail: () async => onSendEmailPressed(context), + downloadReport: () async { + + + + await context.read().downloadReport(widget.trackerType, + onSuccess: (report) async { + + if (report.isEmpty) {} + String path = await Utils.createFileFromString(report, "pdf"); + try { + OpenFilex.open(path); + } catch (ex) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Cannot open file"), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }); + + + + // .downloadBloodSugerReport(); + }, title: _getPageTitle(), bottomChild: Consumer( builder: (context, viewModel, child) { diff --git a/lib/presentation/health_trackers/health_trackers_page.dart b/lib/presentation/health_trackers/health_trackers_page.dart index e974773c..e066b106 100644 --- a/lib/presentation/health_trackers/health_trackers_page.dart +++ b/lib/presentation/health_trackers/health_trackers_page.dart @@ -84,7 +84,7 @@ class _HealthTrackersPageState extends State { iconBgColor: AppColors.primaryRedColor, icon: AppAssets.bloodSugarOnlyIcon, title: LocaleKeys.bloodSugar.tr(context: context), - description: "Track your glucose levels, understand trends, and get personalized insights for better health.", + description: LocaleKeys.trackYourGlucoseLevels.tr(context: context), onTap: () { context.navigateWithName( AppRoutes.healthTrackerDetailPage, diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index 770a5cbe..af6672ea 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/location_util.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -19,6 +20,7 @@ import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/hmg_s 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'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/blood_donation/blood_donation_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; @@ -30,6 +32,7 @@ import 'package:hmg_patient_app_new/presentation/hmg_services/services_view.dart import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; import 'package:hmg_patient_app_new/presentation/home/service_info_page.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; +import 'package:hmg_patient_app_new/presentation/hmg_services/widgets/weather_widget.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/presentation/parking/paking_page.dart'; import 'package:hmg_patient_app_new/presentation/servicesPriceList/services_price_list_page.dart'; @@ -47,47 +50,46 @@ import '../../core/dependencies.dart' show getIt; import '../../features/qr_parking/qr_parking_view_model.dart'; import '../emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart'; -class ServicesPage extends StatelessWidget { +class ServicesPage extends StatefulWidget { bool showBackIcon; ServicesPage({super.key, this.showBackIcon = true}); + @override + State createState() => _ServicesPageState(); +} + +class _ServicesPageState extends State { late BloodDonationViewModel bloodDonationViewModel; + late MedicalFileViewModel medicalFileViewModel; + late WeatherMonitorViewModel weatherVM; + late final List hmgServices = [ HmgServicesComponentModel(11, LocaleKeys.emergencyServices.tr(), "", AppAssets.emergency_services_icon, bgColor: AppColors.primaryRedColor, true, route: null, onTap: () async { // if (getIt.get().isAuthenticated) { - // getIt.get().flushData(); - // getIt.get().getTransportationOrders( - // showLoader: false, - // ); - // getIt.get().getRRTOrders( - // showLoader: false, - // ); - Navigator.of(getIt.get().navigatorKey.currentContext!).push( - CustomPageRoute( - page: EmergencyServicesPage(), - settings: const RouteSettings(name: '/EmergencyServicesPage'), - ), - ); + // getIt.get().flushData(); + // getIt.get().getTransportationOrders( + // showLoader: false, + // ); + // getIt.get().getRRTOrders( + // showLoader: false, + // ); + Navigator.of(getIt.get().navigatorKey.currentContext!).push( + CustomPageRoute( + page: EmergencyServicesPage(), + settings: const RouteSettings(name: '/EmergencyServicesPage'), + ), + ); // } else { // await getIt.get().onLoginPressed(); // } }), - HmgServicesComponentModel( - 11, - LocaleKeys.bookAppointment.tr(), - "", - AppAssets.appointment_calendar_icon, - bgColor: AppColors.bookAppointment, - true, - route: null, - onTap: () { - getIt.get().onTabChanged(0); - Navigator.of(getIt().navigatorKey.currentContext!).push(CustomPageRoute(page: BookAppointmentPage())); - } - ), + HmgServicesComponentModel(11, LocaleKeys.bookAppointment.tr(), "", AppAssets.appointment_calendar_icon, bgColor: AppColors.bookAppointment, true, route: null, onTap: () { + getIt.get().onTabChanged(0); + Navigator.of(getIt().navigatorKey.currentContext!).push(CustomPageRoute(page: BookAppointmentPage())); + }), HmgServicesComponentModel(5, LocaleKeys.completeCheckup.tr(), "", AppAssets.comprehensiveCheckup, bgColor: AppColors.bgGreenColor, true, route: null, onTap: () async { if (getIt.get().isAuthenticated) { getIt.get().pushPageRoute(AppRoutes.comprehensiveCheckupPage); @@ -103,51 +105,51 @@ class ServicesPage extends StatelessWidget { ); } }), - HmgServicesComponentModel( - 11, - LocaleKeys.indoorNavigation.tr(), - "", - AppAssets.indoor_nav_icon, - bgColor: Color(0xff45A2F8), - true, - route: null, - onTap: () { - showCommonBottomSheetWithoutHeight( - title: LocaleKeys.selectHospital.tr(), - GetIt.instance().navigatorKey.currentContext!, - child: ChangeNotifierProvider( - create: (context) => HospitalSelectionBottomSheetViewModel(getIt()), - child: Consumer( - builder: (_, vm, __) => HospitalBottomSheetBody( - searchText: vm.searchController, - displayList: vm.displayList, - onFacilityClicked: (value) { - vm.setSelectedFacility(value); - vm.getDisplayList(); - }, - onHospitalClicked: (hospital) { - Navigator.pop(GetIt.instance().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().navigatorKey.currentContext!.read().clearSearchText(); - }, - ); - }, - ), + // HmgServicesComponentModel( + // 11, + // LocaleKeys.indoorNavigation.tr(), + // "", + // AppAssets.indoor_nav_icon, + // bgColor: Color(0xff45A2F8), + // true, + // route: null, + // onTap: () { + // showCommonBottomSheetWithoutHeight( + // title: LocaleKeys.selectHospital.tr(), + // GetIt.instance().navigatorKey.currentContext!, + // child: ChangeNotifierProvider( + // create: (context) => HospitalSelectionBottomSheetViewModel(getIt()), + // child: Consumer( + // builder: (_, vm, __) => HospitalBottomSheetBody( + // searchText: vm.searchController, + // displayList: vm.displayList, + // onFacilityClicked: (value) { + // vm.setSelectedFacility(value); + // vm.getDisplayList(); + // }, + // onHospitalClicked: (hospital) { + // Navigator.pop(GetIt.instance().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().navigatorKey.currentContext!.read().clearSearchText(); + // }, + // ); + // }, + // ), HmgServicesComponentModel(11, LocaleKeys.eReferralServices.tr(), "", AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async { if (getIt.get().isAuthenticated) { getIt.get().pushPageRoute(AppRoutes.eReferralPage); @@ -164,33 +166,31 @@ class ServicesPage extends StatelessWidget { } }), HmgServicesComponentModel( - 3, - LocaleKeys.bloodDonation.tr(context: getIt.get().navigatorKey.currentContext!), - "".needTranslation, - AppAssets.blood_donation_icon, - bgColor: AppColors.bloodDonationCardColor, - true, - route: null, - onTap: () { - if (getIt.get().isAuthenticated) { - Navigator.of(getIt().navigatorKey.currentContext!).push( - CustomPageRoute( - page: BloodDonationPage(), - ), - ); - } else { - Navigator.of(getIt().navigatorKey.currentContext!).push( - CustomPageRoute( - page: ServiceInfoPage( - serviceName: LocaleKeys.bloodDonation.tr(), - serviceHeader: LocaleKeys.bloodDonationServiceHeader.tr(), - serviceDescription: LocaleKeys.bloodDonationServiceDescription.tr(), - serviceImage: AppAssets.bloodDonationService), - ), - ); - } + 3, + LocaleKeys.bloodDonation.tr(context: getIt.get().navigatorKey.currentContext!), + "".needTranslation, + AppAssets.blood_donation_icon, + bgColor: AppColors.bloodDonationCardColor, + true, + route: null, onTap: () { + if (getIt.get().isAuthenticated) { + Navigator.of(getIt().navigatorKey.currentContext!).push( + CustomPageRoute( + page: BloodDonationPage(), + ), + ); + } else { + Navigator.of(getIt().navigatorKey.currentContext!).push( + CustomPageRoute( + page: ServiceInfoPage( + serviceName: LocaleKeys.bloodDonation.tr(), + serviceHeader: LocaleKeys.bloodDonationServiceHeader.tr(), + serviceDescription: LocaleKeys.bloodDonationServiceDescription.tr(), + serviceImage: AppAssets.bloodDonationService), + ), + ); } - ), + }), HmgServicesComponentModel( 113, LocaleKeys.servicePriceList.tr(context: getIt.get().navigatorKey.currentContext!), @@ -375,22 +375,34 @@ class ServicesPage extends StatelessWidget { ), ]; + @override + void initState() { + // TODO: implement initState + super.initState(); + weatherVM = getIt(); + weatherVM.initiateFetchWeather(); + } + + + @override Widget build(BuildContext context) { bloodDonationViewModel = Provider.of(context); medicalFileViewModel = Provider.of(context); - hmgServices.removeWhere((element) => Utils.havePrivilege(element.action) == false); - return Scaffold( - backgroundColor: AppColors.bgScaffoldColor, - body: CollapsingListView( - title: LocaleKeys.exploreServices.tr(), - isLeading: showBackIcon, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 16.h), + return ChangeNotifierProvider.value( + value: weatherVM, + child: Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: LocaleKeys.exploreServices.tr(), + isLeading: widget.showBackIcon, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const WeatherWidget(), + SizedBox(height: 16.h), LocaleKeys.medicalAndCareServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h), GridView.builder( @@ -440,173 +452,176 @@ class ServicesPage extends StatelessWidget { ), ), SizedBox(height: 24.h), - getIt.get().isAuthenticated ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.personalServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), - SizedBox(height: 16.h), - Row( - children: [ - Expanded( - child: Container( - height: 183.h, - width: 183.h, - padding: EdgeInsets.all(16.w), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.r, - hasShadow: false, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - spacing: 8.w, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h, applyThemeColor: false), - LocaleKeys.habibWallet.tr().toText14(isBold: true, maxlines: 2).expanded, - Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), - ], - ), - Spacer(), - getIt.get().isAuthenticated - ? Consumer(builder: (context, habibWalletVM, child) { - return Utils.getPaymentAmountWithSymbol2(num.parse(NumberFormat.decimalPattern().format(habibWalletVM.habibWalletAmount)), isExpanded: false, letterSpacing: -1) - .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); - }) - : LocaleKeys.loginToViewWalletBalance.tr().toText12(isBold: true, maxLine: 2), - Spacer(), - getIt.get().isAuthenticated - ? CustomButton( - height: 40.h, - icon: AppAssets.recharge_icon, - iconSize: 24.w, - iconColor: AppColors.infoColor, - textColor: AppColors.infoColor, - text: LocaleKeys.recharge.tr(), - borderWidth: 0.w, - isBold: true, - borderColor: Colors.transparent, - backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08), - padding: EdgeInsets.all(8.w), - fontSize: 14.f, - onPressed: () { - Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); - }, - ) - : SizedBox.shrink(), - ], - ).onPress(() async { - if (getIt.get().isAuthenticated) { - Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage())); - } else { - await getIt.get().onLoginPressed(); - } - }), - ), - ), - SizedBox(width: 16.w), - Expanded( - child: Container( - height: 183.h, - width: 183.h, - padding: EdgeInsets.all(16.w), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.r, - hasShadow: false, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - spacing: 8.w, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.services_medical_file_icon, width: 40.w, height: 40.h, applyThemeColor: false), - LocaleKeys.medicalFile.tr().toText16(isBold: true, maxlines: 2).expanded, - Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), - ], + getIt.get().isAuthenticated + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.personalServices.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: Container( + height: 183.h, + width: 183.h, + padding: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 8.w, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.wallet, width: 40.w, height: 40.h, applyThemeColor: false), + LocaleKeys.habibWallet.tr().toText14(isBold: true, maxlines: 2).expanded, + Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), + ], + ), + Spacer(), + getIt.get().isAuthenticated + ? Consumer(builder: (context, habibWalletVM, child) { + return Utils.getPaymentAmountWithSymbol2(num.parse(NumberFormat.decimalPattern().format(habibWalletVM.habibWalletAmount)), + isExpanded: false, letterSpacing: -1) + .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.r, width: 80.w, height: 24.h); + }) + : LocaleKeys.loginToViewWalletBalance.tr().toText12(isBold: true, maxLine: 2), + Spacer(), + getIt.get().isAuthenticated + ? CustomButton( + height: 40.h, + icon: AppAssets.recharge_icon, + iconSize: 24.w, + iconColor: AppColors.infoColor, + textColor: AppColors.infoColor, + text: LocaleKeys.recharge.tr(), + borderWidth: 0.w, + isBold: true, + borderColor: Colors.transparent, + backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08), + padding: EdgeInsets.all(8.w), + fontSize: 14.f, + onPressed: () { + Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage())); + }, + ) + : SizedBox.shrink(), + ], + ).onPress(() async { + if (getIt.get().isAuthenticated) { + Navigator.of(context).push(CustomPageRoute(page: HabibWalletPage())); + } else { + await getIt.get().onLoginPressed(); + } + }), ), - Spacer(), - getIt.get().isAuthenticated - ? Wrap( - spacing: -12.h, - // runSpacing: 0.h, - children: [ - Utils.buildImgWithAssets( - icon: AppAssets.babyGirlImg, - height: 32.h, - width: 32.w, - border: 1, - fit: BoxFit.contain, - borderRadius: 50.r, - ), - Utils.buildImgWithAssets( - icon: AppAssets.femaleImg, - height: 32.h, - width: 32.w, - border: 1, - borderRadius: 50.r, - fit: BoxFit.contain, - ), - Utils.buildImgWithAssets( - icon: AppAssets.maleImg, - height: 32.h, - width: 32.w, - border: 1, - borderRadius: 50.r, - fit: BoxFit.contain, - ), - ], - ) - : LocaleKeys.loginToViewMedicalFile.tr().toText12(isBold: true, maxLine: 2), - Spacer(), - getIt.get().isAuthenticated - ? CustomButton( - height: 40.h, - icon: AppAssets.add_icon, - iconSize: 24.h, - iconColor: AppColors.primaryRedColor, - textColor: AppColors.primaryRedColor, - text: LocaleKeys.addMember.tr(), - borderWidth: 0.w, - isBold: true, - borderColor: Colors.transparent, - backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.08), - padding: EdgeInsets.all(8.w), - fontSize: 14.f, - onPressed: () { - DialogService dialogService = getIt.get(); - medicalFileViewModel.clearAuthValues(); - dialogService.showAddFamilyFileSheet( - label: LocaleKeys.addFamilyMember.tr(), - message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(), - onVerificationPress: () { - medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); - }); - }, - ) - : SizedBox.shrink(), - ], - ).onPress(() async { - if (getIt.get().isAuthenticated) { - Navigator.of(context).push( - CustomPageRoute( - page: MedicalFilePage(), + ), + SizedBox(width: 16.w), + Expanded( + child: Container( + height: 183.h, + width: 183.h, + padding: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: false, ), - ); - } else { - await getIt.get().onLoginPressed(); - } - }), - ), - ), - ], - ).paddingSymmetrical(24.w, 0), - ], - ) : SizedBox(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + spacing: 8.w, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.services_medical_file_icon, width: 40.w, height: 40.h, applyThemeColor: false), + LocaleKeys.medicalFile.tr().toText16(isBold: true, maxlines: 2).expanded, + Utils.buildSvgWithAssets(icon: getIt.get().isArabic() ? AppAssets.arrow_back : AppAssets.arrow_forward), + ], + ), + Spacer(), + getIt.get().isAuthenticated + ? Wrap( + spacing: -12.h, + // runSpacing: 0.h, + children: [ + Utils.buildImgWithAssets( + icon: AppAssets.babyGirlImg, + height: 32.h, + width: 32.w, + border: 1, + fit: BoxFit.contain, + borderRadius: 50.r, + ), + Utils.buildImgWithAssets( + icon: AppAssets.femaleImg, + height: 32.h, + width: 32.w, + border: 1, + borderRadius: 50.r, + fit: BoxFit.contain, + ), + Utils.buildImgWithAssets( + icon: AppAssets.maleImg, + height: 32.h, + width: 32.w, + border: 1, + borderRadius: 50.r, + fit: BoxFit.contain, + ), + ], + ) + : LocaleKeys.loginToViewMedicalFile.tr().toText12(isBold: true, maxLine: 2), + Spacer(), + getIt.get().isAuthenticated + ? CustomButton( + height: 40.h, + icon: AppAssets.add_icon, + iconSize: 24.h, + iconColor: AppColors.primaryRedColor, + textColor: AppColors.primaryRedColor, + text: LocaleKeys.addMember.tr(), + borderWidth: 0.w, + isBold: true, + borderColor: Colors.transparent, + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.08), + padding: EdgeInsets.all(8.w), + fontSize: 14.f, + onPressed: () { + DialogService dialogService = getIt.get(); + medicalFileViewModel.clearAuthValues(); + dialogService.showAddFamilyFileSheet( + label: LocaleKeys.addFamilyMember.tr(), + message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(), + onVerificationPress: () { + medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms); + }); + }, + ) + : SizedBox.shrink(), + ], + ).onPress(() async { + if (getIt.get().isAuthenticated) { + Navigator.of(context).push( + CustomPageRoute( + page: MedicalFilePage(), + ), + ); + } else { + await getIt.get().onLoginPressed(); + } + }), + ), + ), + ], + ).paddingSymmetrical(24.w, 0), + ], + ) + : SizedBox(), SizedBox(height: 24.h), LocaleKeys.healthTools.tr().toText18(isBold: true).paddingSymmetrical(24.w, 0), SizedBox(height: 16.h), @@ -829,6 +844,7 @@ class ServicesPage extends StatelessWidget { ], ), ), + ), ); } } diff --git a/lib/presentation/hmg_services/widgets/weather_widget.dart b/lib/presentation/hmg_services/widgets/weather_widget.dart new file mode 100644 index 00000000..ca365111 --- /dev/null +++ b/lib/presentation/hmg_services/widgets/weather_widget.dart @@ -0,0 +1,263 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart'; +import 'package:hmg_patient_app_new/features/weather/models/waether_cities_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/weather/weather_details_page.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:provider/provider.dart'; + +class WeatherWidget extends StatelessWidget { + final bool showButton; + + const WeatherWidget({super.key, this.showButton = true}); + + String _getWeatherIcon(String? categoryValue) { + if (categoryValue == null) return "assets/images/svg/cloudyweather.svg"; + + final lowerValue = categoryValue.toLowerCase(); + if (lowerValue.contains('sunny') || lowerValue.contains('clear')) { + return "assets/images/svg/morning.svg"; + } else if (lowerValue.contains('cloudy') || lowerValue.contains('cloud')) { + return "assets/images/svg/cloudyweather.svg"; + } else if (lowerValue.contains('rain')) { + return "assets/images/svg/cloudyweather.svg"; + } + return "assets/images/svg/cloudyweather.svg"; + } + + String _formatDateTime(DateTime dateTime) { + final formatter = DateFormat('MMMM dd, yyyy - EEEE, HH:mm', 'en_US'); + return formatter.format(dateTime); + } + + + @override + Widget build(BuildContext context) { + return Selector cityInfoList})>( + selector: (_, vm) => (isLoading: vm.isLoading, isLocationAvailable: vm.isLocationAvailable, cityInfoList: vm.cityInfoList), + builder: (context, state, child) { + if (!state.isLocationAvailable) { + return noWeatherWidget(context).paddingSymmetrical(24.w, 0.w); + } + if (state.isLoading) { + return _buildLoadingState().paddingSymmetrical(24.w, 0.w); + } + + + final currentCity = state.cityInfoList.isNotEmpty ? state.cityInfoList.first : null; + final now = DateTime.now(); + final temperature = currentCity?.temperature; + final healthTip = LocaleKeys.healthTipsBasedOnCurrentWeather.tr(); + + return Row( + children: [ + Flexible( + child: Container( + padding: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, + ), + child: Column( + children: [ + Row( + children: [ + Utils.buildSvgWithAssets( + icon: _getWeatherIcon(currentCity?.categoryValue), + width: 64.w, + height: 64.w, + ), + SizedBox(width: 12.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _formatDateTime(now).toText12( + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ), + SizedBox(height: 4.w), + "$temperature°C".toText16( + isBold: true, + weight: FontWeight.w600, + ), + SizedBox(height: 4.w), + healthTip.toText12( + color: AppColors.greyTextColor, + fontWeight: FontWeight.w500, + maxLine: 2, + ), + ], + ), + ), + ], + ), + if (showButton) ...[ + SizedBox(height: 16.w), + CustomButton( + text: LocaleKeys.viewDetails.tr(context: context), + onPressed: () { + Navigator.of(context).push( + CustomPageRoute( + page: ChangeNotifierProvider.value( + value: context.read(), + child: const WeatherDetailsPage(), + ), + ), + ); + }, + padding: EdgeInsets.zero, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 10.r, + height: 40.h, + ), + ], + ], + ), + ), + ), + ], + ).paddingSymmetrical(24.w, 0.w); + }, + ); + } + + Widget noWeatherWidget(BuildContext context){ + return Container( + padding: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + hasShadow: false, + ), + child:Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + + Container( + height: 48.h, + width: 48.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.greyColor, + borderRadius: 12.r, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(12.h), + child: Utils.buildSvgWithAssets( + icon: AppAssets.location_unavailable_filled, + fit: BoxFit.contain, + applyThemeColor: false + ), + ), + ), + + + + SizedBox(width: 12.w), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + _formatDateTime(DateTime.now()).toText12( + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ), + SizedBox(height: 4.w), + + "--".toText16( + isBold: true, + weight: FontWeight.w600, + ), + SizedBox(height: 4.w), + LocaleKeys.locationServicesDisabled.tr().toText12( + color: AppColors.greyTextColor, + fontWeight: FontWeight.w500, + maxLine: 2, + ), + ], + ), + ), + ], + )).onPress((){ + context.read().fetchCityInfoList(); + }); + } + + Widget _buildLoadingState() { + return Column( + children: [ + Row( + children: [ + Container( + width: 64.w, + height: 64.w, + decoration: BoxDecoration( + color: AppColors.lightGrayBGColor, + borderRadius: BorderRadius.circular(12.r), + ), + ), + SizedBox(width: 12.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + height: 12.h, + decoration: BoxDecoration( + color: AppColors.lightGrayBGColor, + borderRadius: BorderRadius.circular(6.r), + ), + ), + SizedBox(height: 8.h), + Container( + width: 100.w, + height: 16.h, + decoration: BoxDecoration( + color: AppColors.lightGrayBGColor, + borderRadius: BorderRadius.circular(8.r), + ), + ), + SizedBox(height: 8.h), + Container( + width: double.infinity, + height: 12.h, + decoration: BoxDecoration( + color: AppColors.lightGrayBGColor, + borderRadius: BorderRadius.circular(6.r), + ), + ), + ], + ), + ), + ], + ), + SizedBox(height: 16.w), + Container( + height: 56.h, + decoration: BoxDecoration( + color: AppColors.lightGrayBGColor, + borderRadius: BorderRadius.circular(10.r), + ), + ), + ], + ); + } +} + diff --git a/lib/presentation/home/data/landing_page_data.dart b/lib/presentation/home/data/landing_page_data.dart index a60a7c18..53bea1e8 100644 --- a/lib/presentation/home/data/landing_page_data.dart +++ b/lib/presentation/home/data/landing_page_data.dart @@ -12,8 +12,8 @@ class LandingPageData { ServiceCardData( serviceName: "emergency", icon: AppAssets.emergency_services_icon, - title: LocaleKeys.emergency, - subtitle: LocaleKeys.services2, + title: getIt.get().isArabic()?LocaleKeys.services3 :LocaleKeys.emergency2, + subtitle: getIt.get().isArabic()?LocaleKeys.emergency2:LocaleKeys.services3, backgroundColor: AppColors.primaryRedColor, iconColor: Colors.white, textColor: Colors.white, @@ -23,7 +23,7 @@ class LandingPageData { serviceName: "indoor_navigation", icon: AppAssets.indoor_nav_icon, title: LocaleKeys.indoor, - subtitle: LocaleKeys.navigation, + subtitle: LocaleKeys.thehospital, backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -32,7 +32,7 @@ class LandingPageData { ServiceCardData( serviceName: "search_doctor", icon: AppAssets.search_doctor_icon, - title: LocaleKeys.search, + title: LocaleKeys.finda, subtitle: LocaleKeys.doctor, backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, @@ -42,8 +42,8 @@ class LandingPageData { ServiceCardData( serviceName: "health_calculators_and_converts", icon: AppAssets.health_calculators_icon, - title: LocaleKeys.health, - subtitle: LocaleKeys.calculators, + title: getIt.get().isArabic()? LocaleKeys.calculators:LocaleKeys.health , + subtitle: getIt.get().isArabic()? LocaleKeys.health:LocaleKeys.calculators, backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -52,8 +52,8 @@ class LandingPageData { ServiceCardData( serviceName: "health_converters", icon: AppAssets.health_converters_icon, - title: LocaleKeys.health, - subtitle: LocaleKeys.converters, + title: getIt.get().isArabic()? LocaleKeys.converters:LocaleKeys.health, + subtitle: getIt.get().isArabic()? LocaleKeys.health:LocaleKeys.converters, backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -62,8 +62,8 @@ class LandingPageData { ServiceCardData( serviceName: "parking_guide", icon: AppAssets.car_parking_icon, - title: LocaleKeys.parking, - subtitle: LocaleKeys.guide, + title: getIt.get().isArabic()? LocaleKeys.guide:LocaleKeys.parking2, + subtitle: getIt.get().isArabic()? LocaleKeys.parking2:LocaleKeys.guide, backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -71,12 +71,12 @@ class LandingPageData { ), ]; - static List get getLoggedInServiceCardsList => [ + List get getLoggedInServiceCardsList => [ ServiceCardData( serviceName: "emergency", icon: AppAssets.emergency_services_icon, - title: LocaleKeys.emergency, - subtitle: LocaleKeys.services2, + title: getIt.get().isArabic()?LocaleKeys.services3 :LocaleKeys.emergency2, + subtitle: getIt.get().isArabic()?LocaleKeys.emergency2:LocaleKeys.services3, backgroundColor: AppColors.primaryRedColor, iconColor: Colors.white, textColor: Colors.white, @@ -85,8 +85,8 @@ class LandingPageData { ServiceCardData( serviceName: "lab_results", icon: AppAssets.home_lab_result_icon, - title: LocaleKeys.lab, - subtitle: LocaleKeys.labSubtitle, + title: getIt.get().isArabic()?LocaleKeys.labSubtitle:LocaleKeys.lab, + subtitle: getIt.get().isArabic()?LocaleKeys.lab:LocaleKeys.labSubtitle , backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -95,8 +95,8 @@ class LandingPageData { ServiceCardData( serviceName: "radiology_results", icon: AppAssets.my_radiology_icon, - title: LocaleKeys.radiology, - subtitle: LocaleKeys.radiologySubtitle, + title: getIt.get().isArabic()?LocaleKeys.labSubtitle:LocaleKeys.radiology, + subtitle: getIt.get().isArabic()?LocaleKeys.radiology:LocaleKeys.labSubtitle , backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -105,8 +105,8 @@ class LandingPageData { ServiceCardData( serviceName: "prescriptions", icon: AppAssets.my_prescription_icon, - title: LocaleKeys.medicinesSubtitle, - subtitle: LocaleKeys.details, + title: getIt.get().isArabic()?LocaleKeys.medicinesSubtitle:LocaleKeys.medicalKey, + subtitle: getIt.get().isArabic()?LocaleKeys.medicalKey:LocaleKeys.medicinesSubtitle, backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -115,8 +115,8 @@ class LandingPageData { ServiceCardData( serviceName: "insurance_update", icon: AppAssets.insurance_update_icon, - title: LocaleKeys.insurance, - subtitle: LocaleKeys.details, + title: getIt.get().isArabic()?LocaleKeys.insurance :LocaleKeys.insurance, + subtitle: getIt.get().isArabic()?LocaleKeys.medical: LocaleKeys.details2, backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -135,8 +135,8 @@ class LandingPageData { ServiceCardData( serviceName: "sick_leaves", icon: AppAssets.my_sick_leave_icon, - title: LocaleKeys.sick, - subtitle: LocaleKeys.sickSubtitle, + title: getIt.get().isArabic()?LocaleKeys.sickSubtitle:LocaleKeys.sick2, + subtitle: getIt.get().isArabic()?LocaleKeys.sick2:LocaleKeys.sickSubtitle, backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, @@ -145,8 +145,8 @@ class LandingPageData { ServiceCardData( serviceName: "my_invoice", icon: AppAssets.invoiceListIcon, - title: LocaleKeys.invoiceList, - subtitle: LocaleKeys.myDoctorSubtitle, + title: getIt.get().isArabic()?LocaleKeys.myDoctorSubtitle2:LocaleKeys.invoiceList2, + subtitle: getIt.get().isArabic()?LocaleKeys.invoiceList2:LocaleKeys.myDoctorSubtitle2, backgroundColor: AppColors.whiteColor, iconColor: AppColors.blackColor, textColor: AppColors.blackColor, diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index f103bd3f..bd574bfa 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -510,7 +510,12 @@ class _LandingPageState extends State { pagination: SwiperPagination( alignment: Alignment.bottomCenter, margin: EdgeInsets.only(top: 220.h + 20 + 8 + 24), - builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor), + builder: _DirectionalDotPaginationBuilder( + color: Color(0xffD9D9D9), + activeColor: AppColors.blackBgColor, + isRTL: isArabic, + totalCount: swiperItemCount, + ), ), itemBuilder: (BuildContext context, int index) { final int actualIndex = isArabic ? (swiperItemCount - 1 - index) : index; @@ -625,7 +630,7 @@ class _LandingPageState extends State { LocaleKeys.quickLinks.tr(context: context).toText16(isBold: true), Row( children: [ - LocaleKeys.viewMedicalFile.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), + LocaleKeys.viewMedicalFileLandingPage.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), SizedBox(width: 2.h), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), ], @@ -696,7 +701,7 @@ class _LandingPageState extends State { .width / 2.5 - 10), child: ListView.separated( scrollDirection: Axis.horizontal, - itemCount: LandingPageData.getLoggedInServiceCardsList.length, + itemCount: LandingPageData().getLoggedInServiceCardsList.length, shrinkWrap: true, controller: _horizontalScrollController, padding: EdgeInsets.only(left: 0.h, right: 0.h, top: 16.h, bottom: 12.h), @@ -708,14 +713,14 @@ class _LandingPageState extends State { horizontalOffset: 100.0, child: FadeInAnimation( child: SmallServiceCard( - icon: LandingPageData.getLoggedInServiceCardsList[index].icon, - title: LandingPageData.getLoggedInServiceCardsList[index].title, - subtitle: LandingPageData.getLoggedInServiceCardsList[index].subtitle, - iconColor: LandingPageData.getLoggedInServiceCardsList[index].iconColor!, - textColor: LandingPageData.getLoggedInServiceCardsList[index].textColor, - backgroundColor: LandingPageData.getLoggedInServiceCardsList[index].backgroundColor, - isBold: LandingPageData.getLoggedInServiceCardsList[index].isBold, - serviceName: LandingPageData.getLoggedInServiceCardsList[index].serviceName, + icon: LandingPageData().getLoggedInServiceCardsList[index].icon, + title: LandingPageData().getLoggedInServiceCardsList[index].title, + subtitle: LandingPageData().getLoggedInServiceCardsList[index].subtitle, + iconColor: LandingPageData().getLoggedInServiceCardsList[index].iconColor!, + textColor: LandingPageData().getLoggedInServiceCardsList[index].textColor, + backgroundColor: LandingPageData().getLoggedInServiceCardsList[index].backgroundColor, + isBold: LandingPageData().getLoggedInServiceCardsList[index].isBold, + serviceName: LandingPageData().getLoggedInServiceCardsList[index].serviceName, ), ), ), @@ -793,7 +798,7 @@ class _LandingPageState extends State { LocaleKeys.services2.tr(context: context).toText18(isBold: true), Row( children: [ - LocaleKeys.viewAllServices.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), + LocaleKeys.viewAll.tr(context: context).toText14(color: AppColors.primaryRedColor, isBold: true), SizedBox(width: 2.h), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 14.h), ], @@ -1414,3 +1419,53 @@ class _LandingPageState extends State { ); } } + +/// A custom pagination builder that reverses the active-dot index +/// when the UI is in RTL (Arabic) mode, so the dots visually match +/// the swipe direction. +class _DirectionalDotPaginationBuilder extends SwiperPlugin { + final Color color; + final Color activeColor; + final double size; + final double activeSize; + final double space; + final bool isRTL; + final int totalCount; + + const _DirectionalDotPaginationBuilder({ + required this.color, + required this.activeColor, + required this.isRTL, + required this.totalCount, + this.size = 10.0, + this.activeSize = 10.0, + this.space = 3.0, + }); + + @override + Widget build(BuildContext context, SwiperPluginConfig config) { + final int activeIndex = isRTL + ? (totalCount - 1 - config.activeIndex) + : config.activeIndex; + print("the index is $activeIndex"); + print("the config.activeIndex is ${config.activeIndex}"); + + return Row( + mainAxisSize: MainAxisSize.min, + textDirection: ui.TextDirection.ltr, // always lay out dots left-to-right + children: List.generate(config.itemCount, (i) { + final bool isActive = i == config.activeIndex; + return Container( + margin: EdgeInsets.symmetric(horizontal: space), + width: isActive ? activeSize : size, + height: isActive ? activeSize : size, + decoration: BoxDecoration( + color: isActive ? activeColor : color, + shape: BoxShape.circle, + ), + ); + }), + ); + } +} + diff --git a/lib/presentation/home/widgets/habib_wallet_card.dart b/lib/presentation/home/widgets/habib_wallet_card.dart index 2a45e2f5..88c2f2b1 100644 --- a/lib/presentation/home/widgets/habib_wallet_card.dart +++ b/lib/presentation/home/widgets/habib_wallet_card.dart @@ -26,7 +26,7 @@ class HabibWalletCard extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - LocaleKeys.myBalance.tr(context: context).toText16(isBold: true), + LocaleKeys.myBalanceSubtitle.tr(context: context).toText16(isBold: true), // Row( // children: [ // LocaleKeys.viewAllServices.tr(context: context).toText12(color: AppColors.primaryRedColor, isBold: true), diff --git a/lib/presentation/home_health_care/hhc_procedures_page.dart b/lib/presentation/home_health_care/hhc_procedures_page.dart index 2ece0598..fca3f9c4 100644 --- a/lib/presentation/home_health_care/hhc_procedures_page.dart +++ b/lib/presentation/home_health_care/hhc_procedures_page.dart @@ -12,6 +12,7 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_view_ import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; +import 'package:hmg_patient_app_new/features/location/location_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_order_detail_page.dart'; import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_selection_review_page.dart'; @@ -395,9 +396,11 @@ class _HhcProceduresPageState extends State { final navigationServices = getIt.get(); final appState = getIt.get(); final hmgServicesViewModel = context.read(); + if (hmgServicesViewModel.selectedHhcServices.isNotEmpty) { - hmgServicesViewModel.setSelectedServiceForHhcOrder(hmgServicesViewModel.selectedHhcServices.first); + context.read().placeValueInController(); + hmgServicesViewModel.setSelectedServiceForHhcOrder(hmgServicesViewModel.selectedHhcServices.first); bool result = await navigationServices.push( CustomPageRoute( page: MapUtilityScreen( diff --git a/lib/presentation/insurance/insurance_approval_details_page.dart b/lib/presentation/insurance/insurance_approval_details_page.dart index 6589bc4e..6d8e2524 100644 --- a/lib/presentation/insurance/insurance_approval_details_page.dart +++ b/lib/presentation/insurance/insurance_approval_details_page.dart @@ -53,8 +53,16 @@ class InsuranceApprovalDetailsPage extends StatelessWidget { children: [ AppCustomChipWidget( labelText: insuranceApprovalResponseModel.approvalStatusDescption, - backgroundColor: insuranceApprovalResponseModel.status == 9 ? AppColors.successColor.withOpacity(0.1) : AppColors.primaryRedColor.withOpacity(0.1), - textColor: insuranceApprovalResponseModel.status == 9 ? AppColors.successColor : AppColors.primaryRedColor, + backgroundColor: insuranceApprovalResponseModel.status == 9 + ? AppColors.successColor.withValues(alpha: 0.1) + : (insuranceApprovalResponseModel.approvalStatusDescption?.toLowerCase().contains('partial') == true || insuranceApprovalResponseModel.status == 8) + ? AppColors.warningColorYellow.withValues(alpha: 0.1) + : AppColors.primaryRedColor.withValues(alpha: 0.1), + textColor: insuranceApprovalResponseModel.status == 9 + ? AppColors.successColor + : (insuranceApprovalResponseModel.approvalStatusDescption?.toLowerCase().contains('partial') == true || insuranceApprovalResponseModel.status == 8) + ? AppColors.warningColorYellow + : AppColors.primaryRedColor, ), AppCustomChipWidget( icon: (!insuranceApprovalResponseModel.isLiveCareAppointment! ? AppAssets.walkin_appointment_icon : AppAssets.small_livecare_icon), @@ -65,7 +73,7 @@ class InsuranceApprovalDetailsPage extends StatelessWidget { ), AppCustomChipWidget( labelText: appState.isArabic() ? insuranceApprovalResponseModel.isInOutPatientDescriptionN! : insuranceApprovalResponseModel.isInOutPatientDescription!, - backgroundColor: AppColors.warningColorYellow.withOpacity(0.1), + backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.1), textColor: AppColors.warningColorYellow, ), ], diff --git a/lib/presentation/insurance/insurance_home_page.dart b/lib/presentation/insurance/insurance_home_page.dart index 985de895..2bbe6f7d 100644 --- a/lib/presentation/insurance/insurance_home_page.dart +++ b/lib/presentation/insurance/insurance_home_page.dart @@ -47,7 +47,7 @@ class _InsuranceHomePageState extends State { appState = getIt.get(); insuranceViewModel = Provider.of(context, listen: false); return CollapsingListView( - title: "${LocaleKeys.insurance.tr(context: context)} ${LocaleKeys.updateInsurance.tr(context: context)}", + title: "${LocaleKeys.updateInsurance.tr(context: context)} ${LocaleKeys.insurance.tr(context: context)}", // history: () { // // insuranceViewModel.setIsInsuranceHistoryLoading(true); // // insuranceViewModel.getPatientInsuranceCardHistory(); diff --git a/lib/presentation/insurance/widgets/insurance_approval_card.dart b/lib/presentation/insurance/widgets/insurance_approval_card.dart index b50e199f..a12a26b6 100644 --- a/lib/presentation/insurance/widgets/insurance_approval_card.dart +++ b/lib/presentation/insurance/widgets/insurance_approval_card.dart @@ -24,6 +24,34 @@ class InsuranceApprovalCard extends StatelessWidget { AppState appState; bool isLoading; + /// Helper method to determine if approval is partially approved + bool _isPartiallyApproved() { + final description = insuranceApprovalResponseModel.approvalStatusDescption?.toLowerCase() ?? ''; + return description.contains('partial') || insuranceApprovalResponseModel.status == 8; + } + + /// Helper method to get chip background color based on approval status + Color _getChipBackgroundColor() { + if (insuranceApprovalResponseModel.status == 9) { + return AppColors.successColor.withValues(alpha: 0.1); + } else if (_isPartiallyApproved()) { + return AppColors.warningColorYellow.withValues(alpha: 0.1); + } else { + return AppColors.primaryRedColor.withValues(alpha: 0.1); + } + } + + /// Helper method to get chip text color based on approval status + Color _getChipTextColor() { + if (insuranceApprovalResponseModel.status == 9) { + return AppColors.successColor; + } else if (_isPartiallyApproved()) { + return AppColors.warningColorYellow; + } else { + return AppColors.primaryRedColor; + } + } + @override Widget build(BuildContext context) { return Container( @@ -45,8 +73,8 @@ class InsuranceApprovalCard extends StatelessWidget { children: [ AppCustomChipWidget( labelText: isLoading ? "Approved" : insuranceApprovalResponseModel.approvalStatusDescption, - backgroundColor: insuranceApprovalResponseModel.status == 9 ? AppColors.successColor.withOpacity(0.1) : AppColors.primaryRedColor.withOpacity(0.1), - textColor: insuranceApprovalResponseModel.status == 9 ? AppColors.successColor : AppColors.primaryRedColor, + backgroundColor: isLoading ? AppColors.successColor.withValues(alpha: 0.1) : _getChipBackgroundColor(), + textColor: isLoading ? AppColors.successColor : _getChipTextColor(), ).toShimmer2(isShow: isLoading), AppCustomChipWidget( icon: isLoading ? AppAssets.walkin_appointment_icon : (!insuranceApprovalResponseModel.isLiveCareAppointment! ? AppAssets.walkin_appointment_icon : AppAssets.small_livecare_icon), @@ -69,7 +97,7 @@ class InsuranceApprovalCard extends StatelessWidget { : appState.isArabic() ? insuranceApprovalResponseModel.isInOutPatientDescriptionN! : insuranceApprovalResponseModel.isInOutPatientDescription!, - backgroundColor: AppColors.warningColorYellow.withOpacity(0.1), + backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.1), textColor: AppColors.warningColorYellow, ).toShimmer2(isShow: isLoading), ], diff --git a/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart b/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart index 7ae38db1..ad2ad77f 100644 --- a/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart +++ b/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart @@ -2,6 +2,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; @@ -58,7 +60,7 @@ class LabOrderResultItem extends StatelessWidget { style: TextStyle( fontSize: 24.f, fontWeight: FontWeight.w600, - fontFamily: 'Poppins', + fontFamily: getIt.get().isArabic() ? 'CairoArabic' : 'Poppins', color: tests!.checkIfGraphShouldBeDisplayed() ? context.read().getColor( tests?.calculatedResultFlag ?? "", @@ -77,7 +79,7 @@ class LabOrderResultItem extends StatelessWidget { style: TextStyle( fontSize: 12.f, fontWeight: FontWeight.w600, - fontFamily: 'Poppins', + fontFamily: getIt.get().isArabic() ? 'CairoArabic' : 'Poppins', color: AppColors.greyTextColor, ), softWrap: true, @@ -91,35 +93,41 @@ class LabOrderResultItem extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Visibility( - visible: tests?.checkIfGraphShouldBeDisplayed() == true, - child: Column( - spacing: 6.h, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - severityText.tr().toText10(isBold: true, color: AppColors.greyTextColor), - Utils.buildSvgWithAssets(icon: AppAssets.lab_result_indicator, width: 21, height: 23, iconColor: iconColor), - ], + Flexible( + flex: 2, + child: Visibility( + visible: tests?.checkIfGraphShouldBeDisplayed() == true, + child: Column( + spacing: 6.h, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + severityText.tr().toText10(isBold: true, color: AppColors.greyTextColor), + Utils.buildSvgWithAssets(icon: AppAssets.lab_result_indicator, width: 21, height: 23, iconColor: iconColor), + ], + ), ), ), - Visibility( - visible: tests?.checkIfGraphShouldBeDisplayed() == true, - child: CustomButton( - icon: AppAssets.view_report_icon, - iconColor: AppColors.infoColor, - iconSize: 16.h, - text: LocaleKeys.viewReport.tr(context: context), - onPressed: () { - onTap(); - }, - backgroundColor: AppColors.infoColor.withAlpha(20), - borderColor: AppColors.infoColor.withAlpha(0), - textColor: AppColors.infoColor, - fontSize: 14, - fontWeight: FontWeight.w600, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, + Flexible( + flex:2, + child: Visibility( + visible: tests?.checkIfGraphShouldBeDisplayed() == true, + child: CustomButton( + icon: AppAssets.view_report_icon, + iconColor: AppColors.infoColor, + iconSize: 16.h, + text: LocaleKeys.viewReport.tr(context: context), + onPressed: () { + onTap(); + }, + backgroundColor: AppColors.infoColor.withAlpha(20), + borderColor: AppColors.infoColor.withAlpha(0), + textColor: AppColors.infoColor, + fontSize: 14, + fontWeight: FontWeight.w600, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), ), ), ], diff --git a/lib/presentation/lab/lab_results/lab_result_details.dart b/lib/presentation/lab/lab_results/lab_result_details.dart index fb5fd583..9fbb49a8 100644 --- a/lib/presentation/lab/lab_results/lab_result_details.dart +++ b/lib/presentation/lab/lab_results/lab_result_details.dart @@ -286,7 +286,7 @@ class _LabResultDetailsState extends State { style: TextStyle( fontSize: 12.f, fontWeight: FontWeight.w600, - fontFamily: 'Poppins', + fontFamily: getIt.get().isArabic() ? 'CairoArabic' : 'Poppins', color: AppColors.greyTextColor, ), softWrap: true, @@ -364,7 +364,7 @@ class _LabResultDetailsState extends State { value, style: TextStyle( fontWeight: FontWeight.w600, - fontFamily: 'Poppins', + fontFamily: getIt.get().isArabic() ? 'CairoArabic' : 'Poppins', fontSize: 8.f, color: AppColors.textColor, ), @@ -376,7 +376,7 @@ class _LabResultDetailsState extends State { padding: const EdgeInsets.only(top: 8.0), child: Text( label, - style: TextStyle(fontSize: 8.f, fontFamily: 'Poppins', fontWeight: FontWeight.w600, color: AppColors.labelTextColor), + style: TextStyle(fontSize: 8.f, fontFamily: getIt.get().isArabic() ? 'CairoArabic' : 'Poppins', fontWeight: FontWeight.w600, color: AppColors.labelTextColor), ), ); } diff --git a/lib/presentation/lab/lab_results/lab_result_list_item.dart b/lib/presentation/lab/lab_results/lab_result_list_item.dart index 908d129d..78369463 100644 --- a/lib/presentation/lab/lab_results/lab_result_list_item.dart +++ b/lib/presentation/lab/lab_results/lab_result_list_item.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart' ; import 'package:hmg_patient_app_new/core/app_export.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/utils.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -25,7 +27,7 @@ class LabHistoryItem extends StatelessWidget{ style: TextStyle( fontSize: 14.f, fontWeight: FontWeight.w600, - fontFamily: 'Poppins', + fontFamily: getIt.get().isArabic() ? 'CairoArabic' : 'Poppins', color: AppColors.labelTextColor ), ), @@ -34,7 +36,7 @@ class LabHistoryItem extends StatelessWidget{ style: TextStyle( fontSize: 18.f, fontWeight: FontWeight.w600, - fontFamily: 'Poppins', + fontFamily: getIt.get().isArabic() ? 'CairoArabic' : 'Poppins', color: AppColors.textColor ), ) diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 89839f0d..9d8c6cc1 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -27,6 +27,7 @@ import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart'; import 'package:hmg_patient_app_new/features/monthly_report/monthly_report_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; @@ -59,6 +60,7 @@ import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_ca import 'package:hmg_patient_app_new/presentation/medical_file/widgets/health_tracker_menu_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/health_tools_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_report_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_sick_leave_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_report/medical_reports_page.dart'; import 'package:hmg_patient_app_new/presentation/monthly_report/monthly_report.dart'; @@ -132,6 +134,8 @@ class _MedicalFilePageState extends State { medicalFileViewModel.setIsPatientSickLeaveListLoading(true); medicalFileViewModel.getPatientSickLeaveList(); medicalFileViewModel.onTabChanged(0); + medicalFileViewModel.setIsPatientMedicalReportsLoading(true); + medicalFileViewModel.getPatientMedicalReportList(); // Load vital signs hmgServicesViewModel.getPatientVitalSign(); } @@ -743,7 +747,7 @@ class _MedicalFilePageState extends State { Expanded( child: LabRadCard( icon: AppAssets.radiology_icon, - labelText: "${LocaleKeys.radiology.tr(context: context)} ${LocaleKeys.results.tr(context: context)}", + labelText: LocaleKeys.radiologyLabResults.tr(context: context), // labOrderTests: ["Complete blood count", "Creatinine", "Blood Sugar", // labOrderTests: ["Chest X-ray", "Abdominal Ultrasound", "Dental X-ray"], labOrderTests: [], @@ -1077,7 +1081,7 @@ class _MedicalFilePageState extends State { ); }), MedicalFileCard( - label: "Allergy Information".needTranslation, + label: LocaleKeys.illurgyInfomation.tr(), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.allergy_info_icon, @@ -1092,7 +1096,7 @@ class _MedicalFilePageState extends State { ); }), MedicalFileCard( - label: "Vaccine Information".needTranslation, + label: LocaleKeys.vaccineInfomation.tr(), textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.vaccine_info_icon, @@ -1187,7 +1191,7 @@ class _MedicalFilePageState extends State { Navigator.of(context).push(CustomPageRoute(page: InsuranceHomePage())); }), MedicalFileCard( - label: "${LocaleKeys.insurance.tr(context: context)} ${LocaleKeys.approvals.tr(context: context)}", + label: "${LocaleKeys.approvals1.tr(context: context)} ${LocaleKeys.insurance.tr(context: context)}", textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.insurance_approval_icon, @@ -1241,7 +1245,7 @@ class _MedicalFilePageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "${LocaleKeys.sick.tr(context: context)} ${LocaleKeys.sickSubtitle.tr(context: context)}".toText16(isBold: true), + "${LocaleKeys.sickSubtitle.tr(context: context)} ${LocaleKeys.sick2.tr(context: context)}".toText16(isBold: true), Row( children: [ LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor), @@ -1285,65 +1289,78 @@ class _MedicalFilePageState extends State { ).paddingSymmetrical(0.w, 0.h); }), SizedBox(height: 16.h), - GridView( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 10.h, - mainAxisSpacing: 16.w, - mainAxisExtent: 110.h, - ), - physics: NeverScrollableScrollPhysics(), - padding: EdgeInsets.zero, - shrinkWrap: true, - children: [ - // MedicalFileCard( - // label: LocaleKeys.monthlyReports.tr(context: context), - // textColor: AppColors.blackColor, - // backgroundColor: AppColors.whiteColor, - // svgIcon: AppAssets.monthly_reports_icon, - // isLargeText: true, - // iconSize: 36.h, - // ).onPress(() { - // monthlyReportViewModel.setHealthSummaryEnabled(cacheService.getBool(key: CacheConst.isMonthlyReportEnabled) ?? false); - // Navigator.of(context).push( - // CustomPageRoute( - // page: MonthlyReport(), - // ), - // ); - // }), - MedicalFileCard( - label: LocaleKeys.medicalReports.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.medical_reports_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() { - medicalFileViewModel.setIsPatientMedicalReportsLoading(true); - medicalFileViewModel.getPatientMedicalReportList(); - Navigator.of(context).push( - CustomPageRoute( - page: MedicalReportsPage(), - ), - ); - }), - // MedicalFileCard( - // label: LocaleKeys.sickLeaveReport.tr(context: context), - // textColor: AppColors.blackColor, - // backgroundColor: AppColors.whiteColor, - // svgIcon: AppAssets.sick_leave_report_icon, - // isLargeText: true, - // iconSize: 36.h, - // ).onPress(() { - // Navigator.of(context).push( - // CustomPageRoute( - // page: PatientSickleavesListPage(), - // ), - // ); - // }), - ], - ).paddingSymmetrical(0.w, 0.0), - SizedBox(height: 24.h), + Selector listRequest, List listReady})>( + selector: (context, vm) => (isLoading: vm.isPatientMedicalReportsListLoading, listRequest: vm.patientMedicalReportRequestedList, listReady: vm.patientMedicalReportReadyList), + builder: (context, data, _){ + print("isloading are ${data.isLoading}"); + print("listRequest are ${data.listRequest}"); + print("listReady are ${data.listReady}"); + return MedicalReportCard( + isLoading: data.isLoading, listRequest: data.listRequest, listReady: data.listReady + ); + }, + ) + // GridView( + // gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + // crossAxisCount: 3, + // crossAxisSpacing: 10.h, + // mainAxisSpacing: 16.w, + // mainAxisExtent: 110.h, + // ), + // physics: NeverScrollableScrollPhysics(), + // padding: EdgeInsets.zero, + // shrinkWrap: true, + // children: [ + // // MedicalFileCard( + // // label: LocaleKeys.monthlyReports.tr(context: context), + // // textColor: AppColors.blackColor, + // // backgroundColor: AppColors.whiteColor, + // // svgIcon: AppAssets.monthly_reports_icon, + // // isLargeText: true, + // // iconSize: 36.h, + // // ).onPress(() { + // // monthlyReportViewModel.setHealthSummaryEnabled(cacheService.getBool(key: CacheConst.isMonthlyReportEnabled) ?? false); + // // Navigator.of(context).push( + // // CustomPageRoute( + // // page: MonthlyReport(), + // // ), + // // ); + // // }), + // ///todo te changes of the medical report should be displayed here. + // /// + // + // // MedicalFileCard( + // // label: LocaleKeys.medicalReports.tr(context: context), + // // textColor: AppColors.blackColor, + // // backgroundColor: AppColors.whiteColor, + // // svgIcon: AppAssets.medical_reports_icon, + // // isLargeText: true, + // // iconSize: 36.w, + // // ).onPress(() { + // // + // // Navigator.of(context).push( + // // CustomPageRoute( + // // page: MedicalReportsPage(), + // // ), + // // ); + // // }), + // // MedicalFileCard( + // // label: LocaleKeys.sickLeaveReport.tr(context: context), + // // textColor: AppColors.blackColor, + // // backgroundColor: AppColors.whiteColor, + // // svgIcon: AppAssets.sick_leave_report_icon, + // // isLargeText: true, + // // iconSize: 36.h, + // // ).onPress(() { + // // Navigator.of(context).push( + // // CustomPageRoute( + // // page: PatientSickleavesListPage(), + // // ), + // // ); + // // }), + // ], + // ).paddingSymmetrical(0.w, 0.0), + ,SizedBox(height: 24.h), ], ); case 3: @@ -1550,7 +1567,7 @@ class _MedicalFilePageState extends State { icon: AppAssets.heightVital, label: LocaleKeys.height.tr(context: context), value: _formatValue(vitalSign.heightCm), - unit: 'cm', + unit: LocaleKeys.cm.tr(), status: null, onTap: onTap, ), @@ -1568,7 +1585,7 @@ class _MedicalFilePageState extends State { icon: AppAssets.weightVital, label: LocaleKeys.weight.tr(context: context), value: _formatValue(vitalSign.weightKg), - unit: 'kg', + unit: LocaleKeys.kg.tr(), status: vitalSign.weightKg != null ? _getWeightStatus(vitalSign.bodyMassIndex) : null, onTap: onTap, ), diff --git a/lib/presentation/medical_file/patient_sickleaves_list_page.dart b/lib/presentation/medical_file/patient_sickleaves_list_page.dart index 42d94ce7..3f8c0bc2 100644 --- a/lib/presentation/medical_file/patient_sickleaves_list_page.dart +++ b/lib/presentation/medical_file/patient_sickleaves_list_page.dart @@ -53,7 +53,7 @@ class _PatientSickleavesListPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: "${LocaleKeys.sick.tr(context: context)} ${LocaleKeys.sickSubtitle.tr(context: context)}", + title: "${LocaleKeys.sickSubtitle.tr(context: context)} ${LocaleKeys.sick.tr(context: context)}", child: SingleChildScrollView( child: Consumer(builder: (context, model, child) { return Column( diff --git a/lib/presentation/medical_file/vaccine_list_page.dart b/lib/presentation/medical_file/vaccine_list_page.dart index 06f1a6d5..d6c29676 100644 --- a/lib/presentation/medical_file/vaccine_list_page.dart +++ b/lib/presentation/medical_file/vaccine_list_page.dart @@ -50,7 +50,7 @@ class _VaccineListPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: LocaleKeys.vaccineInfo.tr(context: context), + title: LocaleKeys.vaccineInfomation.tr(context: context), child: SingleChildScrollView( child: Consumer(builder: (context, medicalFileVM, child) { return Column( diff --git a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart index 13ea39dd..a67a6765 100644 --- a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -13,14 +14,16 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/appointment_details_page.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/appointment_payment_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'dart:ui' as ui; -class MedicalFileAppointmentCard extends StatelessWidget { +class MedicalFileAppointmentCard extends StatefulWidget { final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; final MyAppointmentsViewModel myAppointmentsViewModel; final Function onRescheduleTap; @@ -34,6 +37,97 @@ class MedicalFileAppointmentCard extends StatelessWidget { required this.onAskDoctorTap, }); + @override + State createState() => _MedicalFileAppointmentCardState(); +} + +class _MedicalFileAppointmentCardState extends State { + Timer? _countdownTimer; + Duration? _timeRemaining; + Function(void Function())? _modalSetState; + + @override + void initState() { + super.initState(); + // Start countdown timer if appointment requires payment + if (widget.patientAppointmentHistoryResponseModel.nextAction == 15) { + _startCountdownTimer(); + } + } + + @override + void dispose() { + _countdownTimer?.cancel(); + super.dispose(); + } + + void _startCountdownTimer() { + final appointmentDate = DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate!); + final expiryDate = appointmentDate.subtract(const Duration(hours: 4)); + + setState(() { + _timeRemaining = expiryDate.difference(DateTime.now()); + }); + + _countdownTimer?.cancel(); + _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) { + final newTimeRemaining = expiryDate.difference(DateTime.now()); + + if (newTimeRemaining.isNegative) { + timer.cancel(); + setState(() { + _timeRemaining = Duration.zero; + }); + _modalSetState?.call(() { + _timeRemaining = Duration.zero; + }); + } else { + setState(() { + _timeRemaining = newTimeRemaining; + }); + _modalSetState?.call(() { + _timeRemaining = newTimeRemaining; + }); + } + }); + } + + // Helper method to build each time unit (number + label) + Widget _buildTimeUnit(String value, String label) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + alignment: Alignment.center, + child: value.toText32( + isBold: true, + color: AppColors.blackColor, + isEnglishOnly: true, + ), + ), + SizedBox(height: 4.h), + Container( + alignment: Alignment.center, + child: label.toText12( + color: AppColors.greyTextColor, + ), + ), + ], + ); + } + + // Helper method to build time separator (:) + Widget _buildTimeSeparator() { + return Padding( + padding: EdgeInsets.only(bottom: 16.h, left: 6.w, right: 6.w), + child: ':'.toText32( + isBold: true, + color: AppColors.blackColor, + ), + ); + } + @override Widget build(BuildContext context) { AppState appState = getIt.get(); @@ -43,17 +137,17 @@ class MedicalFileAppointmentCard extends StatelessWidget { AppCustomChipWidget( richText: Directionality( textDirection: ui.TextDirection.ltr, - child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false) - .toText12(color: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, isBold: true, isEnglishOnly: true) + child: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.patientAppointmentHistoryResponseModel.appointmentDate), false) + .toText12(color: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, isBold: true, isEnglishOnly: true) .paddingSymmetrical(8.w, 0), ), - icon: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppAssets.appointment_calendar_icon : AppAssets.alarm_clock_icon, - iconColor: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, + icon: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppAssets.appointment_calendar_icon : AppAssets.alarm_clock_icon, + iconColor: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, iconSize: 16.w, - backgroundColor: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.greyColor : AppColors.secondaryLightRedColor, - textColor: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, + backgroundColor: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.greyColor : AppColors.secondaryLightRedColor, + textColor: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, padding: EdgeInsets.only(top: 12.h, bottom: 12.h, left: 8.w, right: 8.w), - ).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), + ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), SizedBox(height: 16.h), Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: false), @@ -64,20 +158,20 @@ class MedicalFileAppointmentCard extends StatelessWidget { Row( children: [ Image.network( - patientAppointmentHistoryResponseModel.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", + widget.patientAppointmentHistoryResponseModel.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", width: 25.w, height: 27.h, fit: BoxFit.fill, - ).circle(100).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), + ).circle(100).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), SizedBox(width: 8.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - (patientAppointmentHistoryResponseModel.doctorNameObj ?? "").toText14(isBold: true, maxlines: 1, isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? "")).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), - (patientAppointmentHistoryResponseModel.clinicName ?? "") + (widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "").toText14(isBold: true, maxlines: 1, isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "")).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), + (widget.patientAppointmentHistoryResponseModel.clinicName ?? "") .toText12(maxLine: 1, isBold: true, color: AppColors.greyTextColor) - .toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), + .toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), ], ), ), @@ -86,42 +180,35 @@ class MedicalFileAppointmentCard extends StatelessWidget { SizedBox(height: 12.h), Row( children: [ - myAppointmentsViewModel.isMyAppointmentsLoading + widget.myAppointmentsViewModel.isMyAppointmentsLoading ? Container().toShimmer2(isShow: true, height: 40.h, width: 100.w, radius: 12.r) : Expanded( flex: 7, - child: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) - ? getArrivedAppointmentButton(context).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading) + child: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? getArrivedAppointmentButton(context).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading) : CustomButton( - text: AppointmentType.getNextActionText(patientAppointmentHistoryResponseModel.nextAction), + text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), onPressed: () { - Navigator.of(context) - .push(CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), - )) - .then((val) { - // widget.myAppointmentsViewModel.initAppointmentsViewModel(); - // widget.myAppointmentsViewModel.getPatientAppointments(true, false); - }); + handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction, context); }, - backgroundColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15), - borderColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), - textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), + backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.15), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withValues(alpha: 0.01), + textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), fontSize: 14.f, fontWeight: FontWeight.w600, borderRadius: 12.r, padding: EdgeInsets.symmetric(horizontal: 10.w), height: 40.h, - icon: AppointmentType.getNextActionIcon(patientAppointmentHistoryResponseModel.nextAction), - iconColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), + icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), + iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), iconSize: 14.h, - ).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), + ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading), ), SizedBox(width: 8.w), - ((((patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || - (patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || - !Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID ?? 0))) && - AppointmentType.isArrived(patientAppointmentHistoryResponseModel)) + ((((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || + (widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || + !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID ?? 0))) && + AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) ? SizedBox.shrink() : Expanded( flex: 2, @@ -145,11 +232,11 @@ class MedicalFileAppointmentCard extends StatelessWidget { ), ), ), - ).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading).onPress(() { + ).toShimmer2(isShow: widget.myAppointmentsViewModel.isMyAppointmentsLoading).onPress(() { Navigator.of(context) .push( CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), ), ) .then((val) { @@ -168,22 +255,22 @@ class MedicalFileAppointmentCard extends StatelessWidget { } Widget getArrivedAppointmentButton(BuildContext context) { - if ((((patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || - (patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || - !Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID ?? 0))) && - AppointmentType.isArrived(patientAppointmentHistoryResponseModel)) { + if ((((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || + (widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || + !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID ?? 0))) && + AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) { return CustomButton( text: LocaleKeys.viewDetails.tr(context: context), onPressed: () { Navigator.of(context) .push( CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), ), ) .then((_) { - myAppointmentsViewModel.initAppointmentsViewModel(); - myAppointmentsViewModel.getPatientAppointments(true, false); + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); }); }, backgroundColor: AppColors.secondaryLightRedColor, @@ -200,30 +287,10 @@ class MedicalFileAppointmentCard extends StatelessWidget { iconSize: 16.h, ); } else { - return - // DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15 - // ? CustomButton( - // text: LocaleKeys.askDoctor.tr(context: context), - // onPressed: () { - // onAskDoctorTap(); - // }, - // backgroundColor: AppColors.secondaryLightRedColor, - // borderColor: AppColors.secondaryLightRedColor, - // textColor: AppColors.primaryRedColor, - // fontSize: 14.f, - // fontWeight: FontWeight.w600, - // borderRadius: 12.r, - // padding: EdgeInsets.symmetric(horizontal: 10.w), - // height: 40.h, - // icon: AppAssets.ask_doctor_icon, - // iconColor: AppColors.primaryRedColor, - // iconSize: 16.h, - // ) - // : - CustomButton( + return CustomButton( text: LocaleKeys.rebook.tr(context: context), onPressed: () { - onRescheduleTap(); + widget.onRescheduleTap(); }, backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, @@ -239,4 +306,142 @@ class MedicalFileAppointmentCard extends StatelessWidget { ); } } + + Future handleAppointmentNextAction(nextAction, BuildContext context) async { + switch (nextAction) { + case 0: + // No action needed - go to details + Navigator.of(context) + .push(CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + )) + .then((val) {}); + break; + case 10: + // Confirm appointment - go to details + Navigator.of(context) + .push(CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + )) + .then((val) {}); + break; + case 15: + // Pending payment - show waiting modal + // Ensure timer is running before showing modal + if (_countdownTimer == null || !_countdownTimer!.isActive) { + _startCountdownTimer(); + } + + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.notice.tr(), + child: StatefulBuilder( + builder: (context, setModalState) { + // Store the modal setState callback + _modalSetState = setModalState; + + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Message text + LocaleKeys.upcomingPaymentPending.tr(context: context).toText14( + color: AppColors.textColor, + isCenter: true, + ), + SizedBox(height: 24.h), + // Countdown Timer - DD : HH : MM : SS format with labels + Directionality( + textDirection: ui.TextDirection.ltr, child:Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Days + _buildTimeUnit( + _timeRemaining != null ? _timeRemaining!.inDays.toString().padLeft(2, '0') : '00', + LocaleKeys.days.tr(context: context), + ), + _buildTimeSeparator(), + // Hours + _buildTimeUnit( + _timeRemaining != null ? _timeRemaining!.inHours.remainder(24).toString().padLeft(2, '0') : '00', + LocaleKeys.hours.tr(context: context), + ), + _buildTimeSeparator(), + // Minutes + _buildTimeUnit( + _timeRemaining != null ? _timeRemaining!.inMinutes.remainder(60).toString().padLeft(2, '0') : '00', + LocaleKeys.minutes.tr(context: context), + ), + _buildTimeSeparator(), + // Seconds + _buildTimeUnit( + _timeRemaining != null ? _timeRemaining!.inSeconds.remainder(60).toString().padLeft(2, '0') : '00', + LocaleKeys.seconds.tr(context: context), + ), + ], + )), + SizedBox(height: 24.h), + // Green Acknowledge button with checkmark icon + CustomButton( + text: LocaleKeys.acknowledged.tr(context: context), + onPressed: () { + _modalSetState = null; // Clear callback when closing + Navigator.of(context).pop(); + }, + backgroundColor: AppColors.successColor, + borderColor: AppColors.successColor, + textColor: Colors.white, + fontSize: 16.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + height: 50.h, + icon: AppAssets.checkmark_icon, + iconColor: Colors.white, + ), + ], + ); + }, + ), + callBackFunc: () { + _modalSetState = null; // Clear callback when closing + }, + isFullScreen: false, + isCloseButtonVisible: true, + ); + break; + case 20: + // Pay now - navigate to payment page + widget.myAppointmentsViewModel.setIsPatientAppointmentShareLoading(true); + Navigator.of(context).push( + CustomPageRoute( + page: AppointmentPaymentPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + ), + ); + break; + case 50: + // Confirm livecare - go to details + Navigator.of(context) + .push(CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + )) + .then((val) {}); + break; + case 90: + // Check-in - go to details + Navigator.of(context) + .push(CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + )) + .then((val) {}); + break; + default: + // Default - go to details + Navigator.of(context) + .push(CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + )) + .then((val) {}); + } + } } + diff --git a/lib/presentation/medical_file/widgets/medical_report_card.dart b/lib/presentation/medical_file/widgets/medical_report_card.dart new file mode 100644 index 00000000..4759a65b --- /dev/null +++ b/lib/presentation/medical_file/widgets/medical_report_card.dart @@ -0,0 +1,101 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/medical_report/medical_reports_page.dart'; +import 'package:hmg_patient_app_new/presentation/monthly_report/monthly_report.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; + +class MedicalReportCard extends StatelessWidget { + final bool isLoading; + final List listRequest; + final List listReady; + final VoidCallback? onViewDetails; + + const MedicalReportCard({ + super.key, + required this.isLoading, + required this.listRequest, + required this.listReady, + this.onViewDetails, + }); + + + + @override + Widget build(BuildContext context) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.medicalReport.tr().toText18( + isBold: true, + weight: FontWeight.w700, + ).toShimmer2(isShow: isLoading), + SizedBox(height: 12.h), + Wrap( + spacing: 8.w, + runSpacing: 8.h, + children: [ + if ( listRequest.isNotEmpty ) + AppCustomChipWidget( + labelText: '${listRequest.length.toString().padLeft(2, '0')} ${LocaleKeys.requested.tr()}', + icon: AppAssets.document, + iconColor: AppColors.alertColor, + textColor: AppColors.alertColor, + backgroundColor: AppColors.warningColorYellow.withOpacity(.2), + iconSize: 14.w, + ), + if (listReady.isNotEmpty) + AppCustomChipWidget( + labelText: '${listRequest.length.toString().padLeft(2, '0')} ${LocaleKeys.readyToDownload.tr()}', + icon: AppAssets.sucess, + iconColor: AppColors.successColor, + textColor: AppColors.successColor, + backgroundColor: AppColors.switchBackgroundColor, + iconSize: 14.w, + ), + ], + ).toShimmer2(isShow: isLoading), + SizedBox(height: 16.h), + CustomButton( + text: LocaleKeys.viewDetails.tr(), + icon: AppAssets.report, + borderWidth: 0, + isDisabled: false, + onPressed: (){ + Navigator.of(context).push( + CustomPageRoute( + page: MedicalReportsPage(), + ), + ); + }, + backgroundColor: AppColors.lightRedButtonColor, + borderColor: AppColors.lightRedButtonColor, + textColor: AppColors.primaryRedColor, + iconColor: AppColors.primaryRedColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 12.r, + height: 48.h, + ).toShimmer2(isShow: isLoading), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/medical_report/medical_reports_page.dart b/lib/presentation/medical_report/medical_reports_page.dart index f1b55e02..ee287662 100644 --- a/lib/presentation/medical_report/medical_reports_page.dart +++ b/lib/presentation/medical_report/medical_reports_page.dart @@ -398,14 +398,18 @@ class _MedicalReportsPageState extends State { onConfirmTap: () async { Navigator.pop(context); LoaderBottomSheet.showLoader(); - await medicalFileViewModel.insertRequestForMedicalReport(onSuccess: (val) { + await medicalFileViewModel.insertRequestForMedicalReport(onSuccess: (val) async{ LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.yourMedicalReportRequestSubmittedSuccessfully.tr(context: context)), - callBackFunc: () { + callBackFunc: () async{ medicalFileViewModel.setIsPatientMedicalReportsLoading(true); medicalFileViewModel.onMedicalReportTabChange(0); medicalFileViewModel.getPatientMedicalReportList(); + + }); + await Future.delayed(Duration(milliseconds: 2000)).then((value) { + if (mounted) Navigator.pop(context); }); }, onError: (err) { LoaderBottomSheet.hideLoader(); diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart index c8553207..84406aad 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -9,11 +10,13 @@ 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/core/utils/image_compression_helper.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/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/family_file_response_model.dart'; +import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; @@ -24,6 +27,8 @@ import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/user_avatar_widget.dart'; +import 'package:hmg_patient_app_new/widgets/image_picker.dart'; +import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; class FamilyCards extends StatefulWidget { @@ -58,6 +63,9 @@ class FamilyCards extends StatefulWidget { class _FamilyCardsState extends State { AppState appState = getIt(); late InsuranceViewModel insuranceViewModel; + late ProfileSettingsViewModel profileSettingsViewModel; + File? _selectedImage; + bool _isUploadingImage = false; @override void initState() { @@ -67,6 +75,321 @@ class _FamilyCardsState extends State { super.initState(); } + void _pickImage() { + // Show image picker options without checking permissions first + ImageOptions.showImageOptionsNew( + context, + 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...'); + final compressedFile = await ImageCompressionHelper.compressAndResizeImage(file); + + File finalFile; + String finalBase64; + + if (compressedFile == null) { + print('⚠️ Compression failed - using original file as fallback'); + + // Fallback: use original image if compression fails + final originalSize = await file.length(); + final maxSize = 1048576; // 1MB + + if (originalSize > maxSize) { + print('❌ Original file is too large: ${originalSize / 1024} KB'); + if (mounted) { + Utils.showToast( + LocaleKeys.imageSizeTooLarge.tr(context: context), + ); + } + return; + } + + print('✅ Using original file (${originalSize / 1024} KB)'); + finalFile = file; + var bytes = await file.readAsBytes(); + finalBase64 = base64.encode(bytes); + } else { + // Check compressed file size + final fileSize = await compressedFile.length(); + final maxSize = 1048576; // 1MB + print('✅ Compression successful: ${fileSize / 1024} KB'); + + if (fileSize > maxSize) { + print('❌ Compressed file still too large'); + if (mounted) { + Utils.showToast( + LocaleKeys.imageSizeTooLarge.tr(context: context), + ); + } + return; + } + + finalFile = compressedFile; + var bytes = await compressedFile.readAsBytes(); + finalBase64 = base64.encode(bytes); + } + + print('Converting to base64... Length: ${finalBase64.length}'); + + if (mounted) { + setState(() { + _selectedImage = finalFile; + }); + + print('📤 Starting upload...'); + // Upload the image + _uploadImage(finalBase64); + } + + print('=== Image processing complete ==='); + } catch (e, stackTrace) { + print('❌ Error in _pickImage: $e'); + print('Stack trace: $stackTrace'); + if (mounted) { + Utils.showToast( + LocaleKeys.failedToProcessImage.tr(context: context), + ); + } + } + }, + checkCameraPermission: _checkCameraPermission, + checkGalleryPermission: _checkGalleryPermission, + ); + } + + Future _checkCameraPermission() async { + try { + print('=== Checking camera permission ==='); + + // First check current status + PermissionStatus currentStatus = await Permission.camera.status; + print('Current camera permission status: $currentStatus'); + + // If already granted, return true + if (currentStatus.isGranted) { + print('✅ Camera permission already granted'); + return true; + } + + // If denied or permanently denied, show settings dialog + if (currentStatus.isDenied || currentStatus.isPermanentlyDenied) { + // Request permission first + PermissionStatus newStatus = await Permission.camera.request(); + print('Camera permission after request: $newStatus'); + + if (newStatus.isGranted) { + print('✅ Camera permission granted'); + return true; + } + + // Still denied - show settings dialog + print('⚠️ Camera permission denied - showing settings dialog'); + if (mounted) { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.cameraPermissionMessage.tr(context: context), + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + openAppSettings(); + }, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + return false; + } + + // Request permission for the first time + PermissionStatus newStatus = await Permission.camera.request(); + print('Camera permission after request: $newStatus'); + + if (newStatus.isGranted) { + print('✅ Camera permission granted'); + return true; + } + + // Denied - show settings dialog + print('❌ Camera permission denied - showing settings dialog'); + if (mounted) { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.cameraPermissionMessage.tr(context: context), + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + openAppSettings(); + }, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + return false; + } catch (e) { + print('❌ Error checking camera permission: $e'); + if (mounted) { + Utils.showToast( + LocaleKeys.failedToCheckPermissions.tr(context: context), + ); + } + return false; + } + } + + Future _checkGalleryPermission() async { + try { + print('=== Checking gallery permission ==='); + + // Determine which permission to check based on platform and Android version + Permission galleryPermission; + + if (Platform.isIOS) { + galleryPermission = Permission.photos; + } else { + // Android: use photos permission which handles API level differences automatically + galleryPermission = Permission.photos; + } + + // First check current status + PermissionStatus currentStatus = await galleryPermission.status; + print('Current gallery permission status: $currentStatus'); + + // If already granted, return true + if (currentStatus.isGranted || currentStatus.isLimited) { + print('✅ Gallery permission already granted'); + return true; + } + + // If denied or permanently denied, request permission first + if (currentStatus.isDenied || currentStatus.isPermanentlyDenied) { + // Request permission first + PermissionStatus newStatus = await galleryPermission.request(); + print('Gallery permission after request: $newStatus'); + + if (newStatus.isGranted || newStatus.isLimited) { + print('✅ Gallery permission granted'); + return true; + } + + // Still denied - show settings dialog + print('⚠️ Gallery permission denied - showing settings dialog'); + if (mounted) { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.galleryPermissionMessage.tr(context: context), + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + openAppSettings(); + }, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + return false; + } + + // Request permission for the first time + PermissionStatus newStatus = await galleryPermission.request(); + print('Gallery permission after request: $newStatus'); + + if (newStatus.isGranted || newStatus.isLimited) { + print('✅ Gallery permission granted'); + return true; + } + + // Denied - show settings dialog + print('❌ Gallery permission denied - showing settings dialog'); + if (mounted) { + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: LocaleKeys.galleryPermissionMessage.tr(context: context), + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + openAppSettings(); + }, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + return false; + } catch (e) { + print('❌ Error checking gallery permission: $e'); + if (mounted) { + Utils.showToast( + LocaleKeys.failedToCheckPermissions.tr(context: context), + ); + } + return false; + } + } + + void _uploadImage(String base64String) { + final patientID = appState.getAuthenticatedUser()?.patientId; + + if (patientID != null) { + setState(() { + _isUploadingImage = true; + }); + + profileSettingsViewModel.uploadProfileImage( + patientID: patientID, + imageData: base64String, + onSuccess: (data) { + if (mounted) { + setState(() { + _selectedImage = null; // Clear selected image after successful upload + _isUploadingImage = false; + }); + Utils.showToast( + LocaleKeys.profileImageUpdatedSuccessfully.tr(context: context), + ); + } + }, + onError: (error) { + if (mounted) { + setState(() { + _isUploadingImage = false; + }); + } + Utils.showToast(error); + }, + ); + } + } + double _calculateAspectRatio(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; @@ -96,6 +419,7 @@ class _FamilyCardsState extends State { widget.profileViewList!.addAll(widget.profiles); widget.profileViewList!.removeWhere((element) => element.responseId == appState.getAuthenticatedUser()?.patientId); insuranceViewModel = Provider.of(context, listen: false); + profileSettingsViewModel = Provider.of(context, listen: false); DialogService dialogService = getIt.get(); if (widget.isRequestDesign) { return Column( @@ -192,17 +516,61 @@ class _FamilyCardsState extends State { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.r), child: Padding( padding: EdgeInsets.all(16.w), - child: Column( + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - UserAvatarWidget( - width: 56.w, - height: 56.h, - fit: BoxFit.cover, - isCircular: true, + Stack( + children: [ + UserAvatarWidget( + width: 56.w, + height: 56.h, + fit: BoxFit.cover, + isCircular: true, + ), + // Camera/Edit button + Positioned( + right: 0, + bottom: 0, + child: GestureDetector( + onTap: () { + if (!_isUploadingImage) { + _pickImage(); + } + }, + child: Container( + width: 20.w, + height: 20.h, + decoration: BoxDecoration( + color: AppColors.primaryRedColor, + shape: BoxShape.circle, + border: Border.all( + color: AppColors.whiteColor, + width: 1.5.w, + ), + ), + child: _isUploadingImage + ? SizedBox( + width: 10.w, + height: 10.h, + child: CircularProgressIndicator( + strokeWidth: 1.5.w, + valueColor: AlwaysStoppedAnimation( + AppColors.whiteColor, + ), + ).paddingAll(4.w), + ) + : Icon( + Icons.camera_alt, + color: AppColors.whiteColor, + size: 10.w, + ), + ), + ), + ), + ], ), SizedBox(width: 8.w), Column( @@ -311,10 +679,10 @@ class _FamilyCardsState extends State { } }, backgroundColor: insuranceVM.isInsuranceExpired - ? AppColors.primaryRedColor.withOpacity(0.1) + ? AppColors.primaryRedColor.withValues(alpha: 0.1) : insuranceVM.isInsuranceActive - ? AppColors.successColor.withOpacity(0.1) - : AppColors.warningColorYellow.withOpacity(0.1), + ? AppColors.successColor.withValues(alpha: 0.1) + : AppColors.warningColorYellow.withValues(alpha: 0.1), labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: insuranceVM.isInsuranceActive ? 6.w : 0.w), ).toShimmer2(isShow: insuranceVM.isInsuranceLoading); }), @@ -360,8 +728,9 @@ class _FamilyCardsState extends State { gender: profile.gender, age: profile.age, fit: BoxFit.cover, - customProfileImageData: "", // Prevent showing logged-in user's image for family members + customProfileImageData: profile.patientImageData, isCircular: true, + isFamilyMember: true, // Prevent AppState fallback ), SizedBox(height: 8.h), (profile.patientName ?? "Unknown").toText14(isBold: true, isCenter: true, maxlines: 1), diff --git a/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart b/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart index 3a287a44..b1d7b331 100644 --- a/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart +++ b/lib/presentation/prescriptions/prescription_delivery_order_summary_page.dart @@ -7,11 +7,13 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_delivery_orders_list_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; class PrescriptionDeliveryOrderSummaryPage extends StatelessWidget { @@ -130,6 +132,12 @@ class PrescriptionDeliveryOrderSummaryPage extends StatelessWidget { child: Utils.getSuccessWidget(loadingText: LocaleKeys.requestSubmittedSuccessfully.tr(context: context)), callBackFunc: () { Navigator.of(context).pop(); + prescriptionsViewModel.getPrescriptionOrdersList(); + Navigator.of(context).push( + CustomPageRoute( + page: PrescriptionDeliveryOrdersListPage(), + ), + ); }, title: "", isCloseButtonVisible: true, diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index 715b6e4d..a13dbcdd 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -113,7 +113,7 @@ class _PrescriptionDetailPageState extends State { children: [ AppCustomChipWidget( labelText: getIt.get().isArabic() - ? (widget.prescriptionsResponseModel.isInOutPatientDescriptionN ?? "مراجع للعيادات الخارجية") + ? (widget.prescriptionsResponseModel.isInOutPatientDescriptionN ?? "العيادات الخارجية") : (widget.prescriptionsResponseModel.isInOutPatientDescription ?? "OutPatient"), backgroundColor: AppColors.warningColorYellow.withOpacity(0.1), textColor: AppColors.warningColorYellow, @@ -250,7 +250,7 @@ class _PrescriptionDetailPageState extends State { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionDetails.tr(context: context)); await prescriptionsViewModel.getPrescriptionDetails(widget.prescriptionsResponseModel, onSuccess: (val) { LoaderBottomSheet.hideLoader(); - prescriptionsViewModel.initiatePrescriptionDelivery(); + prescriptionsViewModel.initiatePrescriptionDelivery(context); }, onError: (err) { LoaderBottomSheet.hideLoader(); print(err); diff --git a/lib/presentation/prescriptions/prescription_item_view.dart b/lib/presentation/prescriptions/prescription_item_view.dart index 83c2f7f1..66ba13ed 100644 --- a/lib/presentation/prescriptions/prescription_item_view.dart +++ b/lib/presentation/prescriptions/prescription_item_view.dart @@ -59,7 +59,7 @@ class PrescriptionItemView extends StatelessWidget { runSpacing: 6.h, children: [ AppCustomChipWidget( - labelText: "${LocaleKeys.route.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].route}", + labelText: "${LocaleKeys.routePrescription.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].route}", ).toShimmer2(isShow: isLoading), AppCustomChipWidget( labelText: "${LocaleKeys.frequency.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].frequency}", @@ -68,7 +68,8 @@ class PrescriptionItemView extends StatelessWidget { labelText: "${LocaleKeys.dailyDoses.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].doseDailyQuantity}", ).toShimmer2(isShow: isLoading), AppCustomChipWidget( - labelText: "${LocaleKeys.days.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].days}", isEnglishOnly: true, + labelText: "${LocaleKeys.days.tr(context: context)}: ${isLoading ? "" : prescriptionVM.prescriptionDetailsList[index].days}", + isEnglishOnly: true, ).toShimmer2(isShow: isLoading), ], ).paddingSymmetrical(16.h, 0.h), @@ -112,24 +113,17 @@ class PrescriptionItemView extends StatelessWidget { if (await prescriptionVM.checkIfReminderExistForPrescription(index)) { prescriptionVM.prescriptionDetailsList[index].hasReminder = true; - LoaderBottomSheet.showLoader(loadingText: "Removing Reminders"); - bool resultValue = await calender.checkAndRemoveMultipleItems(id: prescriptionVM.prescriptionDetailsList[index].itemID.toString()); - - prescriptionVM.setPrescriptionItemReminder(newValue, prescriptionVM.prescriptionDetailsList[index]); - LoaderBottomSheet.hideLoader(); - return; + LoaderBottomSheet.showLoader(loadingText: "Removing Reminders"); + bool resultValue = await calender.checkAndRemoveMultipleItems(id: prescriptionVM.prescriptionDetailsList[index].itemID.toString()); + prescriptionVM.setPrescriptionItemReminder(newValue, prescriptionVM.prescriptionDetailsList[index]); + LoaderBottomSheet.hideLoader(); + return; } else { DateTime startDate = DateTime.now(); prescriptionVM.serSelectedTime(startDate); DateTime endDate = DateTime(startDate.year, startDate.month, startDate.day + prescriptionVM.prescriptionDetailsList[index].days!.toInt()); - showCommonBottomSheetWithoutHeight( - context, - child: bottomSheetContent(context), - title: LocaleKeys.timeForFirstReminder.tr(), - - isCloseButtonVisible: true - ); + showCommonBottomSheetWithoutHeight(context, child: bottomSheetContent(context), title: LocaleKeys.timeForFirstReminder.tr(), isCloseButtonVisible: true); // BottomSheetUtils().showReminderBottomSheet( // context, @@ -219,23 +213,23 @@ class PrescriptionItemView extends StatelessWidget { CustomButton( backgroundColor: AppColors.successColor, borderColor: AppColors.successColor, - text: LocaleKeys.confirm.tr(), onPressed: () async { - CalenderUtilsNew calender = CalenderUtilsNew.instance; - bool isEventAdded = await calender.createMultipleEvents( - reminderMinutes: 10, - frequencyNumber: prescriptionVM.prescriptionDetailsList[index].frequencyNumber?.toInt(), - days: prescriptionVM.prescriptionDetailsList[index].days!.toInt(), - orderDate: prescriptionVM.prescriptionDetailsList[index].orderDate!, - itemDescriptionN: prescriptionVM.prescriptionDetailsList[index].itemDescription!, - route: prescriptionVM.prescriptionDetailsList[index].route!, - onFailure: (errorMessage) => prescriptionVM.showError(errorMessage), - prescriptionNumber: prescriptionVM.prescriptionDetailsList[index].itemID.toString(), - scheduleDateTime: prescriptionVM.selectedReminderTime, - ); - prescriptionVM.setPrescriptionItemReminder(isEventAdded, prescriptionVM.prescriptionDetailsList[index]); - Navigator.of(context).pop(); - }) - + text: LocaleKeys.add.tr(), + onPressed: () async { + CalenderUtilsNew calender = CalenderUtilsNew.instance; + bool isEventAdded = await calender.createMultipleEvents( + reminderMinutes: 10, + frequencyNumber: prescriptionVM.prescriptionDetailsList[index].frequencyNumber?.toInt(), + days: prescriptionVM.prescriptionDetailsList[index].days!.toInt(), + orderDate: prescriptionVM.prescriptionDetailsList[index].orderDate!, + itemDescriptionN: prescriptionVM.prescriptionDetailsList[index].itemDescription!, + route: prescriptionVM.prescriptionDetailsList[index].route!, + onFailure: (errorMessage) => prescriptionVM.showError(errorMessage), + prescriptionNumber: prescriptionVM.prescriptionDetailsList[index].itemID.toString(), + scheduleDateTime: prescriptionVM.selectedReminderTime, + ); + prescriptionVM.setPrescriptionItemReminder(isEventAdded, prescriptionVM.prescriptionDetailsList[index]); + Navigator.of(context).pop(); + }) ], ); } diff --git a/lib/presentation/prescriptions/prescriptions_list_page.dart b/lib/presentation/prescriptions/prescriptions_list_page.dart index b88eec30..1291c2da 100644 --- a/lib/presentation/prescriptions/prescriptions_list_page.dart +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -27,7 +27,7 @@ import 'package:provider/provider.dart'; import 'dart:ui' as ui; class PrescriptionsListPage extends StatefulWidget { - const PrescriptionsListPage({super.key}); + const PrescriptionsListPage({super.key}); @override State createState() => _PrescriptionsListPageState(); @@ -195,7 +195,7 @@ class _PrescriptionsListPageState extends State { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPrescriptionDetails.tr(context: context)); await prescriptionsViewModel.getPrescriptionDetails(prescriptionsViewModel.patientPrescriptionOrders[index], onSuccess: (val) { LoaderBottomSheet.hideLoader(); - prescriptionsViewModel.initiatePrescriptionDelivery(); + prescriptionsViewModel.initiatePrescriptionDelivery(context); }); } }, diff --git a/lib/presentation/profile_settings/profile_settings.dart b/lib/presentation/profile_settings/profile_settings.dart index 377014ac..be7ddb54 100644 --- a/lib/presentation/profile_settings/profile_settings.dart +++ b/lib/presentation/profile_settings/profile_settings.dart @@ -422,7 +422,7 @@ class ProfileSettingsState extends State { } } -class FamilyCardWidget extends StatelessWidget { +class FamilyCardWidget extends StatefulWidget { final Function() onAddFamilyMemberPress; final Function(FamilyFileResponseModelLists member) onFamilySwitchPress; final FamilyFileResponseModelLists profile; @@ -434,12 +434,62 @@ class FamilyCardWidget extends StatelessWidget { required this.onFamilySwitchPress(FamilyFileResponseModelLists member), }); + @override + State createState() => _FamilyCardWidgetState(); +} + +class _FamilyCardWidgetState extends State { + @override + void initState() { + super.initState(); + // Sync ProfileSettingsViewModel with AppState when widget is created + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + _syncProfileImageFromAppState(); + } + }); + } + + // @override + // void didChangeDependencies() { + // super.didChangeDependencies(); + // // Also sync when dependencies change (e.g., after user switch) + // WidgetsBinding.instance.addPostFrameCallback((_) { + // if (mounted) { + // _syncProfileImageFromAppState(); + // } + // }); + // } + + void _syncProfileImageFromAppState() { + try { + final profileVm = context.read(); + final appState = getIt.get(); + + final appStateImageData = appState.getProfileImageData; + + // Sync profile image from AppState if it's different + if (appStateImageData != null && appStateImageData.isNotEmpty) { + if (profileVm.profileImageData != appStateImageData) { + print("🔄 Syncing profile image from AppState to ProfileSettingsViewModel"); + profileVm.syncProfileImageFromAppState(); + } + } else if (profileVm.profileImageData != null) { + // AppState is null but ViewModel has data - clear it + print("🧹 Clearing ProfileSettingsViewModel image data (AppState is null)"); + profileVm.clearProfileImageCache(); + } + } catch (e) { + print("⚠️ Error syncing profile image: $e"); + } + } + @override Widget build(BuildContext context) { AppState appState = getIt.get(); - final isActive = (profile.responseId == appState.getAuthenticatedUser()?.patientId); + final isActive = (widget.profile.responseId == appState.getAuthenticatedUser()?.patientId); // final isParentUser = appState.getAuthenticatedUser()?.isParentUser ?? false; - // final canSwitch = isParentUser || (!isParentUser && profile.responseId == appState.getSuperUserID); + // final canSwitch = isParentUser || (!isParentUser && widget.profile.responseId == appState.getSuperUserID); return Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, @@ -453,19 +503,44 @@ class FamilyCardWidget extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - UserAvatarWidget( - width: 56.w, - height: 56.h, - gender: profile.gender, - age: profile.age, - fit: BoxFit.cover, - isCircular: true, + // Wrap in Consumer to rebuild when ProfileSettingsViewModel changes + Consumer( + builder: (context, profileVm, _) { + final currentUserId = appState.getAuthenticatedUser()?.patientId; + final isCurrentUser = widget.profile.responseId == currentUserId; + + if (isCurrentUser) { + // Current user - use AppState directly (like home screen profile icon) + return UserAvatarWidget( + width: 56.w, + height: 56.h, + gender: widget.profile.gender, + age: widget.profile.age, + fit: BoxFit.cover, + // Don't pass customProfileImageData - let it use AppState + isCircular: true, + // Don't pass isFamilyMember - allow AppState fallback + ); + } else { + // Family member - use their specific image data + return UserAvatarWidget( + width: 56.w, + height: 56.h, + gender: widget.profile.gender, + age: widget.profile.age, + fit: BoxFit.cover, + customProfileImageData: widget.profile.patientImageData, + isCircular: true, + isFamilyMember: true, // Prevent AppState fallback + ); + } + }, ), Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - "${profile.patientName}".toText18(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, isEnglishOnly: true), + "${widget.profile.patientName}".toText18(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, isEnglishOnly: true), Wrap( direction: Axis.horizontal, spacing: 4.w, @@ -474,7 +549,7 @@ class FamilyCardWidget extends StatelessWidget { AppCustomChipWidget( labelPadding: EdgeInsetsDirectional.only(start: -6.w, end: 6.w), icon: AppAssets.file_icon, - labelText: "${LocaleKeys.fileno.tr(context: context)}: ${profile.responseId}", + labelText: "${LocaleKeys.fileno.tr(context: context)}: ${widget.profile.responseId}", iconSize: 12.w, ), isActive ? AppCustomChipWidget( @@ -488,7 +563,7 @@ class FamilyCardWidget extends StatelessWidget { ], ).expanded, // Icon(Icons.qr_code, size: 56.h) - Image.network("https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=${profile.responseId.toString()}", fit: BoxFit.contain, height: 56.h, width: 56.w) + Image.network("https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=${widget.profile.responseId.toString()}", fit: BoxFit.contain, height: 56.h, width: 56.w) ], ), SizedBox(height: 4.h), @@ -499,7 +574,7 @@ class FamilyCardWidget extends StatelessWidget { runSpacing: 4.h, children: [ AppCustomChipWidget( - labelText: LocaleKeys.ageYearsOld.tr(namedArgs: {'age': profile.age.toString(), 'yearsOld': LocaleKeys.yearsOld.tr(context: context)}), + labelText: LocaleKeys.ageYearsOld.tr(namedArgs: {'age': widget.profile.age.toString(), 'yearsOld': LocaleKeys.yearsOld.tr(context: context)}), ), // isActive && appState.getAuthenticatedUser()!.bloodGroup != null // ? @@ -598,12 +673,12 @@ class FamilyCardWidget extends StatelessWidget { } Widget _buildParentUserButton(int? currentUserId) { - final canSwitch = profile.responseId != currentUserId; + final canSwitch = widget.profile.responseId != currentUserId; return CustomButton( icon: canSwitch ? AppAssets.switch_user : AppAssets.add_family, text: canSwitch ? LocaleKeys.switchAccount.tr() : LocaleKeys.addANewFamilyMember.tr(), - onPressed: canSwitch ? () => onFamilySwitchPress(profile) : onAddFamilyMemberPress, + onPressed: canSwitch ? () => widget.onFamilySwitchPress(widget.profile) : widget.onAddFamilyMemberPress, backgroundColor: canSwitch ? AppColors.secondaryLightRedColor : AppColors.primaryRedColor, borderColor: canSwitch ? AppColors.secondaryLightRedColor : AppColors.primaryRedColor, textColor: canSwitch ? AppColors.primaryRedColor : Colors.white, @@ -614,7 +689,7 @@ class FamilyCardWidget extends StatelessWidget { } Widget _buildNonParentUserButton(int? superUserId) { - final canSwitchBack = superUserId != null && superUserId == profile.responseId; + final canSwitchBack = superUserId != null && superUserId == widget.profile.responseId; return CustomButton( icon: AppAssets.switch_user, @@ -623,7 +698,7 @@ class FamilyCardWidget extends StatelessWidget { borderColor: canSwitchBack ? AppColors.primaryRedColor : Colors.grey.shade200, textColor: canSwitchBack ? AppColors.whiteColor : AppColors.greyTextColor, iconColor: canSwitchBack ? AppColors.whiteColor : AppColors.greyTextColor, - onPressed: canSwitchBack ? () => onFamilySwitchPress(profile) : () {}, + onPressed: canSwitchBack ? () => widget.onFamilySwitchPress(widget.profile) : () {}, height: isFoldable ? 50.h : 40.h, fontSize: 14.f, ).paddingOnly(top: 12.h, right: 16.w, left: 16.w, bottom: 16.h); diff --git a/lib/presentation/profile_settings/widgets/family_card_widget.dart b/lib/presentation/profile_settings/widgets/family_card_widget.dart index 768fe538..fbd1d58b 100644 --- a/lib/presentation/profile_settings/widgets/family_card_widget.dart +++ b/lib/presentation/profile_settings/widgets/family_card_widget.dart @@ -56,8 +56,9 @@ class FamilyCardWidget extends StatelessWidget { gender: profile.gender, age: profile.age, fit: BoxFit.cover, - customProfileImageData: "", - isCircular: true,// Prevent showing logged-in user's image for family members + customProfileImageData: profile.patientImageData, + isCircular: true, + isFamilyMember: true, // Prevent AppState fallback ), Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/presentation/profile_settings/widgets/profile_picture_widget.dart b/lib/presentation/profile_settings/widgets/profile_picture_widget.dart index 1b63fef3..e7abcb12 100644 --- a/lib/presentation/profile_settings/widgets/profile_picture_widget.dart +++ b/lib/presentation/profile_settings/widgets/profile_picture_widget.dart @@ -1,8 +1,10 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:typed_data'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -28,34 +30,130 @@ class ProfilePictureWidget extends StatefulWidget { class _ProfilePictureWidgetState extends State { final AppState _appState = getIt.get(); File? _selectedImage; + int? _currentPatientId; + bool _isInitialLoadTriggered = false; + + /// Cache decoded image bytes to avoid decoding base64 on every rebuild + Uint8List? _cachedImageBytes; + String? _cachedImageDataHash; @override void initState() { super.initState(); + _currentPatientId = _appState.getAuthenticatedUser()?.patientId; + + // Pre-cache existing image data if available (prevents blink from default → loaded) + _tryCacheExistingImage(); + // Use addPostFrameCallback to ensure widget is built before loading WidgetsBinding.instance.addPostFrameCallback((_) { - if (!mounted) return; + if (!mounted || _isInitialLoadTriggered) return; + _isInitialLoadTriggered = true; - final profileVm = context.read(); + final patientID = _appState.getAuthenticatedUser()?.patientId; - // Only sync if AppState has data and ViewModel doesn't - if (_appState.getProfileImageData != null && - _appState.getProfileImageData!.isNotEmpty && - (profileVm.profileImageData == null || profileVm.profileImageData!.isEmpty)) { - profileVm.syncProfileImageFromAppState(); + if (patientID == null) { + print('⚠️ No authenticated user found'); + return; } - // Only load if no data exists in both AppState and ViewModel - if ((_appState.getProfileImageData == null || _appState.getProfileImageData!.isEmpty) && - (profileVm.profileImageData == null || profileVm.profileImageData!.isEmpty)) { - _loadProfileImage(); - } + // Load fresh data from API + print('📥 Loading fresh profile image from API for patient: $patientID'); + _loadProfileImage(forceRefresh: false); }); } - void _loadProfileImage() { - // Check if profile image is already loaded in AppState - if (_appState.getProfileImageData != null && _appState.getProfileImageData!.isNotEmpty) { + @override + void didChangeDependencies() { + super.didChangeDependencies(); + // Only check for user switch, NOT on initial load (initState handles that) + if (_isInitialLoadTriggered) { + _checkAndUpdateUserImage(); + } + } + + @override + void didUpdateWidget(ProfilePictureWidget oldWidget) { + super.didUpdateWidget(oldWidget); + _checkAndUpdateUserImage(); + } + + /// Pre-cache already-loaded image bytes so we don't flash default avatar + void _tryCacheExistingImage() { + final imageData = _appState.getProfileImageData; + if (imageData != null && imageData.isNotEmpty) { + try { + _cachedImageBytes = base64Decode(imageData); + _cachedImageDataHash = '${imageData.length}_${imageData.hashCode}'; + } catch (_) { + _cachedImageBytes = null; + _cachedImageDataHash = null; + } + } + } + + void _checkAndUpdateUserImage() { + // Check if the authenticated user has changed (family member switch) + final currentPatientId = _appState.getAuthenticatedUser()?.patientId; + + if (currentPatientId != null && currentPatientId != _currentPatientId) { + print('🔄 User switched detected: $_currentPatientId -> $currentPatientId'); + + // Update patient ID IMMEDIATELY before any other operations + final oldPatientId = _currentPatientId; + _currentPatientId = currentPatientId; + + // Clear the old profile image data from BOTH AppState and ViewModel + try { + final profileVm = context.read(); + + print('🧹 Clearing cache for old user: $oldPatientId'); + + // Clear AppState cache first + _appState.clearProfileImageCache(); + + // Then clear ViewModel cache + profileVm.clearProfileImageCache(); + + // Clear local decoded bytes cache + _cachedImageBytes = null; + _cachedImageDataHash = null; + + // Force rebuild to show default avatar immediately + if (mounted) { + setState(() { + _selectedImage = null; // Clear any selected image + }); + } + + print('📥 Loading profile image for new user: $currentPatientId'); + // Load the new user's profile image immediately + profileVm.getProfileImage( + patientID: currentPatientId, + forceRefresh: true, + onSuccess: (data) { + print('✅ Profile image loaded successfully for user: $currentPatientId'); + if (mounted) { + _tryCacheExistingImage(); + setState(() {}); // Force rebuild to show new data + } + }, + onError: (error) { + print('❌ Error loading profile image: $error'); + if (mounted) { + setState(() {}); // Force rebuild to show default avatar + } + }, + ); + } catch (e) { + print('❌ Error in _checkAndUpdateUserImage: $e'); + } + } + } + + 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) { // Image already loaded, no need to call API return; } @@ -64,12 +162,19 @@ class _ProfilePictureWidgetState extends State { final patientID = _appState.getAuthenticatedUser()?.patientId; if (patientID != null) { + print('📥 Loading profile image for patient: $patientID (forceRefresh: $forceRefresh)'); profileVm.getProfileImage( patientID: patientID, + forceRefresh: forceRefresh, onSuccess: (data) { - // Image loaded successfully + print('✅ Profile image loaded successfully'); + if (mounted) { + _tryCacheExistingImage(); + setState(() {}); // Rebuild with new cached bytes + } }, onError: (error) { + print('❌ Error loading profile image: $error'); // Error loading image }, ); @@ -367,6 +472,8 @@ class _ProfilePictureWidgetState extends State { imageData: base64String, onSuccess: (data) { if (mounted) { + // Update cached bytes immediately from the uploaded data + _tryCacheExistingImage(); setState(() { _selectedImage = null; // Clear selected image after successful upload }); @@ -383,8 +490,11 @@ class _ProfilePictureWidgetState extends State { } Widget _buildProfileImage(ProfileSettingsViewModel profileVm) { - final gender = _appState.getAuthenticatedUser()?.gender ?? 1; - final age = _appState.getAuthenticatedUser()?.age ?? 0; + // Always get fresh user data + final currentUser = _appState.getAuthenticatedUser(); + final currentPatientId = currentUser?.patientId; + final gender = currentUser?.gender ?? 1; + final age = currentUser?.age ?? 0; // Determine the default image based on gender and age final String defaultImage; @@ -396,7 +506,7 @@ class _ProfilePictureWidgetState extends State { defaultImage = age < 7 ? AppAssets.babyGirlImg : AppAssets.femaleImg; } - // Show selected image if available + // Show selected image if available (only during upload) if (_selectedImage != null) { return ClipOval( child: Image.file( @@ -408,58 +518,84 @@ class _ProfilePictureWidgetState extends State { ); } - // Check both ViewModel and AppState for profile image - final String? imageData = profileVm.profileImageData ?? _appState.getProfileImageData; + // Use cached decoded bytes — update cache if source data changed + final String? imageData = GetIt.instance().getProfileImageData; + final String? currentHash = (imageData != null && imageData.isNotEmpty) + ? '${imageData.length}_${imageData.hashCode}' + : null; - // Show uploaded image if available - if (imageData != null && imageData.isNotEmpty) { + // Re-decode only if the underlying data actually changed + if (currentHash != null && currentHash != _cachedImageDataHash) { try { - final bytes = base64Decode(imageData); - return ClipOval( - child: Image.memory( - bytes, - width: 136.w, - height: 136.h, - fit: BoxFit.cover, - ), - ); + _cachedImageBytes = base64Decode(imageData!); + _cachedImageDataHash = currentHash; } catch (e) { - print('Error decoding profile image: $e'); - // If decoding fails, show default image - return Image.asset( - defaultImage, + print('❌ Error decoding profile image: $e'); + _cachedImageBytes = null; + _cachedImageDataHash = null; + } + } else if (currentHash == null) { + _cachedImageBytes = null; + _cachedImageDataHash = null; + } + + // Show cached decoded image if available + if (_cachedImageBytes != null) { + return ClipOval( + child: Image.memory( + _cachedImageBytes!, + key: ValueKey('profile_$currentPatientId'), width: 136.w, height: 136.h, - ); - } + fit: BoxFit.cover, + gaplessPlayback: true, // Prevents blink during image rebuild + ), + ); } - // Show default image + // Show default image (no image data or user has no uploaded image) + print('📷 Showing default avatar for user $currentPatientId'); return Image.asset( defaultImage, + key: ValueKey('default_$currentPatientId'), width: 136.w, height: 136.h, + gaplessPlayback: true, ); } @override Widget build(BuildContext context) { + // Removed addPostFrameCallback from build() — it was causing redundant + // _checkAndUpdateUserImage calls on every single rebuild. + // didChangeDependencies + didUpdateWidget already handle user switches. + return Consumer( builder: (context, profileVm, _) { + // If we already have cached bytes, show the image even while "loading" + // to prevent the shimmer→image blink on page open + final bool showShimmer = profileVm.isProfileImageLoading && + _cachedImageBytes == null && + _selectedImage == null; + return Center( child: Stack( children: [ - // Profile Image - profileVm.isProfileImageLoading - ? Container( - width: 136.w, - height: 136.h, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: AppColors.greyTextColor.withValues(alpha: 0.2), - ), - ).toShimmer2(isShow: true) - : _buildProfileImage(profileVm), + // Profile Image — use AnimatedSwitcher to smooth transition + AnimatedSwitcher( + duration: const Duration(milliseconds: 200), + child: showShimmer + ? Container( + key: const ValueKey('shimmer'), + width: 136.w, + height: 136.h, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColors.greyTextColor.withValues(alpha: 0.2), + ), + ).toShimmer2(isShow: true) + : _buildProfileImage(profileVm), + ), // Edit button Positioned( diff --git a/lib/presentation/profile_settings/widgets/update_email_widget.dart b/lib/presentation/profile_settings/widgets/update_email_widget.dart index d2633b93..c1952dd0 100644 --- a/lib/presentation/profile_settings/widgets/update_email_widget.dart +++ b/lib/presentation/profile_settings/widgets/update_email_widget.dart @@ -56,7 +56,7 @@ class _UpdateEmailDialogState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Enter the new email address to be updated: ".toText16(textAlign: TextAlign.start, isBold: true), + LocaleKeys.updateEmailAddressTitle.tr().toText16(textAlign: TextAlign.start, isBold: true), SizedBox(height: 12.h), TextInputWidget( labelText: LocaleKeys.email.tr(), diff --git a/lib/presentation/profile_settings/widgets/update_emergency_contact_widget.dart b/lib/presentation/profile_settings/widgets/update_emergency_contact_widget.dart index e1bb59ad..832938da 100644 --- a/lib/presentation/profile_settings/widgets/update_emergency_contact_widget.dart +++ b/lib/presentation/profile_settings/widgets/update_emergency_contact_widget.dart @@ -56,7 +56,7 @@ class _UpdateEmergencyContactDialogState extends State createState() => _RadiologyAiAnalysisDetailedPageState(); +} + +class _RadiologyAiAnalysisDetailedPageState extends State { + late RadiologyViewModel radiologyProvider; + + List _parseStringToList(String? text) { + if (text == null || text.isEmpty) return []; + // Split by newlines and filter out empty lines + return text.split('\n').where((line) => line.trim().isNotEmpty).map((line) => line.trim()).toList(); + } + + @override + Widget build(BuildContext context) { + radiologyProvider = Provider.of(context, listen: false); + return CollapsingListView( + isClose: true, + title: LocaleKeys.aiAnalysis.tr(context: context), + child: Consumer( + builder: (context, model, child) { + if (model.radiologyAiAnalysisResponse == null) { + return Utils.getNoDataWidget(context); + } + + final analysis = model.radiologyAiAnalysisResponse!; + List expandableItems = []; + + // Parse string fields into lists + final predictions = _parseStringToList(analysis.aiPredictions); + final preventions = _parseStringToList(analysis.prevention); + final riskFactorsText = _parseStringToList(analysis.riskFactors); + + // Summary Section + if (analysis.summary != null && analysis.summary!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + backgroundColor: Colors.transparent, + expandedBackgroundColor: Colors.transparent, + title: LocaleKeys.summary.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + initiallyExpanded: true, + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + side: BorderSide(width: 1, color: Color(0xFF0B85F7)), + ), + padding: EdgeInsets.all(16.r), + child: (analysis.summary ?? "").toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + // Lab Results Section (Radiology Results) + if (analysis.labResults != null && analysis.labResults!.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + backgroundColor: Colors.transparent, + expandedBackgroundColor: Colors.transparent, + title: LocaleKeys.radiologyLabResults.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + side: BorderSide(width: 1, color: Color(0xFF0B85F7)), + ), + padding: EdgeInsets.all(16.r), + child: (analysis.labResults ?? "").toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + // AI Predictions Section + if (predictions.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + backgroundColor: Colors.transparent, + expandedBackgroundColor: Colors.transparent, + title: LocaleKeys.prediction.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + side: BorderSide(width: 1, color: Color(0xFFFFAF15)), + ), + padding: EdgeInsets.all(16.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: predictions.map((prediction) => _buildBulletItem(prediction)).toList(), + ), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + // Risk Factors Section + if (riskFactorsText.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + backgroundColor: Colors.transparent, + expandedBackgroundColor: Colors.transparent, + title: LocaleKeys.riskFactors.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + ...riskFactorsText.asMap().entries.map((entry) { + final riskText = entry.value; + return Column( + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: Color(0xFFFBCB6E).withOpacity(0.10), + borderRadius: 12.r, + side: BorderSide(width: 1, color: Color(0xFFFBCB6E).withOpacity(0.10)), + ), + padding: EdgeInsets.all(16.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.guradIcon), + Padding( + padding: const EdgeInsets.all(8.0), + child: LocaleKeys.riskFactors.tr(context: context).toText16(weight: FontWeight.w700, color: AppColors.blackColor), + ), + ], + ), + SizedBox(height: 8.h), + riskText.toText14( + color: AppColors.textColorLight, + height: 1.5, + weight: FontWeight.w400, + ), + ], + ), + ), + SizedBox(height: 16.h), + ], + ); + }).toList(), + ], + ), + ); + } + + // Prevention Section + if (preventions.isNotEmpty) { + expandableItems.add( + ExpandableListItem( + backgroundColor: Colors.transparent, + expandedBackgroundColor: Colors.transparent, + title: LocaleKeys.prevention.tr(context: context).toText18(weight: FontWeight.w700, color: AppColors.blackColor), + children: [ + SizedBox(height: 10.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + side: BorderSide(width: 1, color: Color(0xFF18C273)), + ), + padding: EdgeInsets.all(16.r), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: preventions.map((prevention) => _buildBulletItem(prevention)).toList(), + ), + ), + SizedBox(height: 8.h), + ], + ), + ); + } + + return Column( + children: [ + if (expandableItems.isEmpty) + Utils.getNoDataWidget(context) + else + CustomExpandableList( + expansionMode: ExpansionMode.exactlyOne, + dividerColor: AppColors.dividerColor, + itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h), + items: expandableItems, + theme: ExpandableListTheme.custom( + defaultTrailingIcon: Utils.buildSvgWithAssets( + icon: AppAssets.arrow_down, + height: 22.h, + width: 22.w, + iconColor: AppColors.textColor, + ), + ), + ).paddingSymmetrical(16.w, 0.0), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(Icons.info_outline, color: AppColors.greyTextColor, size: 20.r), + SizedBox(width: 8.w), + Expanded( + child: ("${LocaleKeys.disclaimer.tr()}:${LocaleKeys.disclaimerRadiology.tr()}") + .toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w400), + ), + ], + ).paddingSymmetrical(16.w, 16.h) + ], + ); + }, + ), + ); + } + + Widget _buildBulletItem(String text) { + return Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: text.toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + ), + ], + ), + ); + } +} + diff --git a/lib/presentation/radiology/radiology_ai_analysis_widget.dart b/lib/presentation/radiology/radiology_ai_analysis_widget.dart index 97d35115..8daf53e0 100644 --- a/lib/presentation/radiology/radiology_ai_analysis_widget.dart +++ b/lib/presentation/radiology/radiology_ai_analysis_widget.dart @@ -16,8 +16,18 @@ class RadiologyAiAnalysisWidget extends StatelessWidget { const RadiologyAiAnalysisWidget({super.key, required this.data}); + List _parseStringToList(String? text) { + if (text == null || text.isEmpty) return []; + // Split by newlines and filter out empty lines + return text.split('\n').where((line) => line.trim().isNotEmpty).map((line) => line.trim()).toList(); + } + @override Widget build(BuildContext context) { + // Parse the string fields into lists + final predictions = _parseStringToList(data.aiPredictions); + final preventions = _parseStringToList(data.prevention); + return Container( decoration: BoxDecoration(borderRadius: BorderRadius.circular(24.r), gradient: AppColors.aiLinearGradient), padding: EdgeInsets.all(1.5.r), // Border thickness @@ -43,7 +53,25 @@ class RadiologyAiAnalysisWidget extends StatelessWidget { ), SizedBox(height: 16.h), if (data.summary != null && data.summary!.isNotEmpty) ...[ + LocaleKeys.suggestion.tr().toText16(weight: FontWeight.w700, color: AppColors.blackColor), + SizedBox(height: 8.h), data.summary!.toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + SizedBox(height: 16.h), + Divider(color: AppColors.dividerColor, thickness: 1), + SizedBox(height: 16.h), + ], + if (predictions.isNotEmpty) ...[ + LocaleKeys.prediction.tr().toText16(weight: FontWeight.w700, color: AppColors.blackColor), + SizedBox(height: 12.h), + ...predictions.map((item) => _buildBulletItem(item)), + SizedBox(height: 16.h), + Divider(color: AppColors.dividerColor, thickness: 1), + SizedBox(height: 16.h), + ], + if (preventions.isNotEmpty) ...[ + LocaleKeys.prevention.tr().toText16(weight: FontWeight.w700, color: AppColors.blackColor), + SizedBox(height: 12.h), + ...preventions.map((item) => _buildBulletItem(item)), SizedBox(height: 20.h), ], Row( @@ -52,14 +80,45 @@ class RadiologyAiAnalysisWidget extends StatelessWidget { Icon(Icons.info_outline, color: AppColors.greyTextColor, size: 20.r), SizedBox(width: 8.w), Expanded( - child: ("${LocaleKeys.disclaimer.tr()}: ${LocaleKeys.thisAboveInfo.tr()}").toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w400), + child: ("${LocaleKeys.disclaimer.tr()}:${LocaleKeys.disclaimerRadiology.tr()}").toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w400), ), ], ), - SizedBox(height: 8.h), + SizedBox(height: 16.h), + CustomButton( + height: 50.h, + text: LocaleKeys.close.tr(context: context), + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + onPressed: () { + getIt.get().closeRadiologyAIAnalysis(); + }, + ), ], ), ), ); } + + Widget _buildBulletItem(String text) { + return Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(top: 8.h, right: 12.w, left: 4.w), + child: Container( + width: 5.r, + height: 5.r, + decoration: BoxDecoration(color: AppColors.blackColor, shape: BoxShape.circle), + ), + ), + Expanded( + child: text.toText14(color: AppColors.textColorLight, height: 1.5, weight: FontWeight.w400), + ), + ], + ), + ); + } } diff --git a/lib/presentation/radiology/radiology_orders_page.dart b/lib/presentation/radiology/radiology_orders_page.dart index 80f6aed5..e8c1cedc 100644 --- a/lib/presentation/radiology/radiology_orders_page.dart +++ b/lib/presentation/radiology/radiology_orders_page.dart @@ -21,6 +21,7 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; import '../../features/radiology/radiology_view_model.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -439,12 +440,25 @@ class _RadiologyOrdersPageState extends State { SizedBox(height: 16.h), CustomButton( text: LocaleKeys.viewReport.tr(), - onPressed: () { - model.navigationService.push( - CustomPageRoute( - page: RadiologyResultPage(patientRadiologyResponseModel: group), - ), - ); + onPressed: () async { + // Check if reportData is empty or null + if (group.reportData == null || group.reportData!.trim().isEmpty) { + // Open image directly + await model.getRadiologyImage(patientRadiologyResponseModel: group); + if (model.radiologyImageURL.isNotEmpty) { + Uri uri = Uri.parse(model.radiologyImageURL); + await launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: ""); + } else { + Utils.showToast(LocaleKeys.noDataAvailable.tr()); + } + } else { + // Navigate to details page + model.navigationService.push( + CustomPageRoute( + page: RadiologyResultPage(patientRadiologyResponseModel: group), + ), + ); + } }, backgroundColor: AppColors.infoColor.withAlpha(20), borderColor: AppColors.infoColor.withAlpha(0), diff --git a/lib/presentation/radiology/radiology_result_page.dart b/lib/presentation/radiology/radiology_result_page.dart index 445bbd45..5b7072e6 100644 --- a/lib/presentation/radiology/radiology_result_page.dart +++ b/lib/presentation/radiology/radiology_result_page.dart @@ -11,10 +11,8 @@ import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/radiology/models/resp_models/patient_radiology_response_model.dart'; -import 'package:hmg_patient_app_new/features/radiology/models/resp_models/radiology_ai_analysis_response_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/radiology/radiology_ai_analysis_widget.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -37,8 +35,6 @@ class RadiologyResultPage extends StatefulWidget { class _RadiologyResultPageState extends State { late RadiologyViewModel radiologyViewModel; - final GlobalKey _aiAnalysisKey = GlobalKey(); - RadiologyAiAnalysisResponse? _previousAiData; @override void initState() { @@ -52,7 +48,7 @@ class _RadiologyResultPageState extends State { void dispose() { // Clear AI analysis data when leaving the screen radiologyViewModel.closeRadiologyAIAnalysis(); - radiologyViewModel.setIsRadiologyAIAnalysisNeedsToBeShown(true); + // radiologyViewModel.setIsRadiologyAIAnalysisNeedsToBeShown(true); super.dispose(); } @@ -63,29 +59,6 @@ class _RadiologyResultPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Consumer(builder: (context, radVM, child) { - // Detect changes in radiologyAiAnalysisResponse - WidgetsBinding.instance.addPostFrameCallback((_) { - if (radVM.radiologyAiAnalysisResponse != null && _previousAiData == null) { - // AI data just became available — scroll to it - final ctx = _aiAnalysisKey.currentContext; - if (ctx != null) { - Scrollable.ensureVisible( - ctx, - duration: const Duration(milliseconds: 400), - curve: Curves.easeOut, - ); - } - } else if (radVM.radiologyAiAnalysisResponse == null && _previousAiData != null) { - // AI data just became null — scroll to top - final scrollable = Scrollable.maybeOf(context); - scrollable?.position.animateTo( - 0, - duration: const Duration(milliseconds: 400), - curve: Curves.easeOut, - ); - } - _previousAiData = radVM.radiologyAiAnalysisResponse; - }); return Column( children: [ @@ -185,11 +158,6 @@ class _RadiologyResultPageState extends State { ).paddingSymmetrical(16.h, 0.h), ), SizedBox(height: 24.h), - if (radVM.radiologyAiAnalysisResponse != null) - RadiologyAiAnalysisWidget( - key: _aiAnalysisKey, - data: radVM.radiologyAiAnalysisResponse!, - ), ], ), ), @@ -229,8 +197,7 @@ class _RadiologyResultPageState extends State { iconSize: 20.h, ).paddingSymmetrical(24.h, 0.h) : SizedBox.shrink(), - Utils.havePrivilege(116) ? radVM.radiologyAiAnalysisResponse == null - ? Container( + Utils.havePrivilege(116) ? Container( height: 56.h, decoration: BoxDecoration( borderRadius: BorderRadius.circular(12.r), @@ -274,7 +241,7 @@ class _RadiologyResultPageState extends State { context.pop(); }, ); - }) : SizedBox.shrink() : SizedBox(height: 24.h), + }) : SizedBox(height: 24.h), ], ), ), diff --git a/lib/presentation/smartwatches/smartwatch_home_page.dart b/lib/presentation/smartwatches/smartwatch_home_page.dart index b6c98ee9..53e562e6 100644 --- a/lib/presentation/smartwatches/smartwatch_home_page.dart +++ b/lib/presentation/smartwatches/smartwatch_home_page.dart @@ -85,7 +85,7 @@ class SmartwatchHomePage extends StatelessWidget { Image.asset("assets/images/png/smartwatches/apple-watch-5.jpg", width: 136.w, height: 136.h).paddingSymmetrical(24.w, 8.h), "Apple Watch".needTranslation.toText16(isBold: true), CustomButton( - text: LocaleKeys.select.tr(context: context), + text: LocaleKeys.selectSmartWatch.tr(context: context), onPressed: () { context.read().setSelectedWatchType(SmartWatchTypes.apple, "assets/images/png/smartwatches/apple-watch-5.jpg"); getIt.get().pushPage(page: SmartwatchInstructionsPage( diff --git a/lib/presentation/symptoms_checker/risk_factors_screen.dart b/lib/presentation/symptoms_checker/risk_factors_screen.dart index 969a884d..49aa5150 100644 --- a/lib/presentation/symptoms_checker/risk_factors_screen.dart +++ b/lib/presentation/symptoms_checker/risk_factors_screen.dart @@ -125,7 +125,7 @@ class _RiskFactorsScreenState extends State { fontSize: 13.f, fontWeight: FontWeight.w600, color: AppColors.greyInfoTextColor, - fontFamily: isArabic ? 'GESSTwo' : 'Poppins' + fontFamily: isArabic ? 'CairoArabic' : 'Poppins' ), children: [ TextSpan( diff --git a/lib/presentation/symptoms_checker/suggestions_screen.dart b/lib/presentation/symptoms_checker/suggestions_screen.dart index 8fc58fd7..60b4df19 100644 --- a/lib/presentation/symptoms_checker/suggestions_screen.dart +++ b/lib/presentation/symptoms_checker/suggestions_screen.dart @@ -199,7 +199,7 @@ class _SuggestionsScreenState extends State { children: [ Expanded( child: CollapsingListView( - title: LocaleKeys.suggestions.tr(), + title: LocaleKeys.suggestions.tr() , leadingCallback: () => context.pop(), child: viewModel.isSuggestionsLoading ? _buildLoadingShimmer() diff --git a/lib/presentation/symptoms_checker/triage_screen.dart b/lib/presentation/symptoms_checker/triage_screen.dart index da35a805..58c03db2 100644 --- a/lib/presentation/symptoms_checker/triage_screen.dart +++ b/lib/presentation/symptoms_checker/triage_screen.dart @@ -80,7 +80,7 @@ class _TriagePageState extends State { } // Case 2: Should stop flag is true OR Case 3: Probability >= 70% OR Case 4: 7 or more questions answered - if (viewModel.shouldStopTriage || highestProbability >= 70.0 || viewModel.triageQuestionCount >= 7) { + if (viewModel.shouldStopTriage || highestProbability >= 70.0 || viewModel.triageQuestionCount >= 30) { // Navigate to results/possible conditions screen context.navigateWithName(AppRoutes.possibleConditionsPage); return; @@ -468,14 +468,14 @@ class _TriagePageState extends State { text: TextSpan( text: LocaleKeys.possibleSymptom.tr(context: context), style: TextStyle( - color: AppColors.greyTextColor, fontWeight: FontWeight.w600, fontSize: 14.f, fontFamily: isArabic ? 'GESSTwo' : 'Poppins'), + color: AppColors.greyTextColor, fontWeight: FontWeight.w600, fontSize: 14.f, fontFamily: isArabic ? 'CairoArabic' : 'Poppins'), children: [ TextSpan( text: suggestedCondition, style: TextStyle( color: AppColors.textColor, fontWeight: FontWeight.w600, - fontSize: 14.f, fontFamily: isArabic ? 'GESSTwo' : 'Poppins'), + fontSize: 14.f, fontFamily: isArabic ? 'CairoArabic' : 'Poppins'), ), ], ), @@ -491,7 +491,7 @@ class _TriagePageState extends State { text: TextSpan( text: "${probability.toStringAsFixed(1)}% ", style: TextStyle( - fontFamily: 'Poppins', + fontFamily: isArabic ? 'CairoArabic' : 'Poppins', color: AppColors.primaryRedColor, fontWeight: FontWeight.w600, fontSize: 14.f, @@ -503,7 +503,7 @@ class _TriagePageState extends State { color: AppColors.textColor, fontWeight: FontWeight.w600, fontSize: 13.f, - fontFamily: isArabic ? 'GESSTwo' : 'Poppins', + fontFamily: isArabic ? 'CairoArabic' : 'Poppins', ), ), ], diff --git a/lib/presentation/symptoms_checker/user_info_selection.dart b/lib/presentation/symptoms_checker/user_info_selection.dart index bd19f31b..9113487d 100644 --- a/lib/presentation/symptoms_checker/user_info_selection.dart +++ b/lib/presentation/symptoms_checker/user_info_selection.dart @@ -159,7 +159,7 @@ class _UserInfoSelectionPageState extends State { } } } else { - name = "Guest"; + name = ""; /// as per the mahmooud instruction, if user is not authenticated, we will not show any name in the greeting message } return Scaffold( @@ -176,11 +176,11 @@ class _UserInfoSelectionPageState extends State { String ageText = displayAge != null ? "$displayAge ${LocaleKeys.years.tr(context: context)}" : LocaleKeys.notSet.tr(context: context); String heightText = (viewModel.selectedHeight != null && viewModel.selectedHeight != 0) - ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}" + ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? LocaleKeys.cm.tr() : LocaleKeys.ft.tr()}" : LocaleKeys.notSet.tr(context: context); String weightText = (viewModel.selectedWeight != null && viewModel.selectedWeight != 0) - ? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}" + ? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? LocaleKeys.kg.tr() : LocaleKeys.lb.tr()}" : LocaleKeys.notSet.tr(context: context); return Column( diff --git a/lib/presentation/tele_consultation/zoom/call_screen.dart b/lib/presentation/tele_consultation/zoom/call_screen.dart index 21836a8a..a2721aba 100644 --- a/lib/presentation/tele_consultation/zoom/call_screen.dart +++ b/lib/presentation/tele_consultation/zoom/call_screen.dart @@ -84,6 +84,7 @@ class _CallScreenState extends State { Color chatTextColor = const Color(0xFFAAAAAA); Widget changeNamePopup; final args = ModalRoute.of(context)!.settings.arguments as CallArguments; + var isFrontCamera = useState(true); useEffect(() { Future.microtask(() async { @@ -93,8 +94,11 @@ class _CallScreenState extends State { try { Map SDKaudioOptions = {"connect": true, "mute": false, "autoAdjustSpeakerVolume": false}; + // Map SDKvideoOptions = { + // "localVideoOn": true, + // }; Map SDKvideoOptions = { - "localVideoOn": true, + "localVideoOn": args.callType == 1 ? true : false, }; JoinSessionConfig joinSession = JoinSessionConfig( sessionName: args.sessionName, @@ -302,6 +306,19 @@ class _CallScreenState extends State { List? remoteUserList = await zoom.session.getRemoteUsers(); var leftUserListJson = jsonDecode(data['leftUsers']) as List; List leftUserLis = leftUserListJson.map((userJson) => ZoomVideoSdkUser.fromJson(userJson)).toList(); + + // If no remote users remain, the other participant has left — end the session + if (remoteUserList == null || remoteUserList.isEmpty) { + await zoom.leaveSession(true); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + return; + } + if (fullScreenUser.value != null) { for (var user in leftUserLis) { { @@ -313,8 +330,8 @@ class _CallScreenState extends State { } else { fullScreenUser.value = mySelf; } - remoteUserList?.add(mySelf!); - users.value = remoteUserList!; + remoteUserList.add(mySelf!); + users.value = remoteUserList; }); final userNameChangedListener = eventListener.addListener(EventType.onUserNameChanged, (data) async { @@ -817,63 +834,38 @@ class _CallScreenState extends State { } void onPressCameraList() async { - List options = []; List cameraList = await zoom.videoHelper.getCameraList(); - for (var camera in cameraList) { - options.add( - ListTile( - title: Text( - camera.deviceName, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - color: Colors.black, - ), - ), - onTap: () async => { - await zoom.videoHelper.switchCamera(camera.deviceId), - Navigator.of(context).pop(), - }, - ), - ); + if (cameraList.isEmpty) return; + + // Find the target camera by toggling between front and back + ZoomVideoSdkCameraDevice? targetCamera; + try { + if (isFrontCamera.value) { + // Currently front, switch to back + targetCamera = cameraList.firstWhere( + (c) => c.deviceName.toLowerCase().contains('back') || c.deviceName.toLowerCase().contains('rear'), + ); + } else { + // Currently back, switch to front + targetCamera = cameraList.firstWhere( + (c) => c.deviceName.toLowerCase().contains('front') || c.deviceName.toLowerCase().contains('facing'), + ); + } + } catch (_) { + targetCamera = null; + } + + // Fallback: if we can't identify by name, just pick the other camera + if (targetCamera == null && cameraList.length >= 2) { + final currentIndex = isFrontCamera.value ? 0 : 1; + final nextIndex = (currentIndex + 1) % cameraList.length; + targetCamera = cameraList[nextIndex]; + } + + if (targetCamera != null) { + await zoom.videoHelper.switchCamera(targetCamera.deviceId); + isFrontCamera.value = !isFrontCamera.value; } - options.add( - ListTile( - title: Text( - "Cancel", - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.normal, - color: Colors.black, - ), - ), - onTap: () async => { - Navigator.of(context).pop(), - }, - ), - ); - showDialog( - context: context, - builder: (context) { - return Dialog( - elevation: 0.0, - insetPadding: const EdgeInsets.symmetric(horizontal: 40), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), - child: SizedBox( - height: options.length * 60, - child: Scrollbar( - child: ListView( - shrinkWrap: true, - scrollDirection: Axis.vertical, - children: ListTile.divideTiles( - context: context, - tiles: options, - ).toList(), - ), - ), - ), - ); - }); } // Future onPressMore() async { diff --git a/lib/presentation/vital_sign/vital_sign_details_page.dart b/lib/presentation/vital_sign/vital_sign_details_page.dart index eb137e47..63b21291 100644 --- a/lib/presentation/vital_sign/vital_sign_details_page.dart +++ b/lib/presentation/vital_sign/vital_sign_details_page.dart @@ -196,7 +196,7 @@ class _VitalSignDetailsPageState extends State { style: TextStyle( fontSize: 12.f, fontWeight: FontWeight.w600, - fontFamily: 'Poppins', + fontFamily: getIt.get().isArabic() ? 'CairoArabic' : 'Poppins', color: AppColors.greyTextColor, ), softWrap: true, @@ -335,7 +335,6 @@ class _VitalSignDetailsPageState extends State { Widget _buildHistoryGraph(List history, {List? secondaryHistory}) { final minY = _minY(history, secondaryHistory: secondaryHistory); final maxY = _maxY(history, secondaryHistory: secondaryHistory); - final scheme = VitalSignUiModel.scheme(status: _statusForLatest(null), label: args.title); final isRTL = getIt.get().isArabic(); return CustomGraph( @@ -344,44 +343,43 @@ class _VitalSignDetailsPageState extends State { makeGraphBasedOnActualValue: true, leftLabelReservedSize: 40, showGridLines: true, - showShadow: true, + showShadow: false, isRTL: isRTL, leftLabelInterval: _leftInterval(history, secondaryHistory: secondaryHistory), maxY: maxY, minY: minY, maxX: history.length.toDouble() - .75, - horizontalInterval: _leftInterval(history, secondaryHistory: secondaryHistory), + horizontalInterval: .01, leftLabelFormatter: (value) { - // Show only numeric labels at regular intervals - return _axisLabel(value.toStringAsFixed(0)); + // Format value to 2 decimal places for consistency + value = double.parse(value.toStringAsFixed(2)); + return _axisLabel(value.toStringAsFixed(2)); }, getDrawingHorizontalLine: (value) { - // Draw reference lines for high/low bounds + value = double.parse(value.toStringAsFixed(2)); + // Draw solid reference lines for high/low bounds (matching lab result style) if (args.high != null && (value - args.high!).abs() < 0.1) { return FlLine( - color: AppColors.bgGreenColor.withOpacity(0.2), + color: AppColors.bgGreenColor.withValues(alpha: 0.6), strokeWidth: 1, - dashArray: [5, 5], ); } if (args.low != null && (value - args.low!).abs() < 0.1) { return FlLine( - color: AppColors.bgGreenColor.withOpacity(0.2), + color: AppColors.bgGreenColor.withValues(alpha: 0.6), strokeWidth: 1, - dashArray: [5, 5], ); } - // Draw grid lines at intervals + // Hide other grid lines return FlLine( - color: AppColors.bgGreenColor.withOpacity(0.2), + color: Colors.transparent, strokeWidth: 1, - dashArray: [5, 5], ); }, - graphColor: AppColors.bgGreenColor, - secondaryGraphColor: AppColors.blueColor, - graphShadowColor: AppColors.lightGreenColor.withOpacity(.4), - graphGridColor: scheme.iconFg, + graphColor: AppColors.textColor.withValues(alpha: 90 / 255), + secondaryGraphColor: AppColors.textColor.withValues(alpha: 90 / 255), + graphShadowColor: AppColors.greyColor.withValues(alpha: 0.5), + graphGridColor: AppColors.textColor.withValues(alpha: 0.4), bottomLabelFormatter: (value, data) { if (data.isEmpty) return const SizedBox.shrink(); if (value == 0) return _bottomLabel(data[value.toInt()].label); @@ -497,32 +495,35 @@ class _VitalSignDetailsPageState extends State { final List ranges = []; + // Below low reference (transparent - matching lab result style) if (args.low != null) { ranges.add( HorizontalRangeAnnotation( y1: minY, y2: args.low!, - color: AppColors.highAndLow.withOpacity(0.05), + color: Colors.transparent, ), ); } + // Normal range (light green shading - matching lab result style) if (args.low != null && args.high != null) { ranges.add( HorizontalRangeAnnotation( y1: args.low!, y2: args.high!, - color: AppColors.bgGreenColor.withOpacity(0.05), + color: AppColors.bgGreenColor.withValues(alpha: 0.05), ), ); } + // Above high reference (transparent - matching lab result style) if (args.high != null) { ranges.add( HorizontalRangeAnnotation( y1: args.high!, y2: maxY, - color: AppColors.criticalLowAndHigh.withOpacity(0.05), + color: Colors.transparent, ), ); } @@ -755,7 +756,7 @@ class _VitalSignDetailsPageState extends State { value, style: TextStyle( fontWeight: FontWeight.w600, - fontFamily: 'Poppins', + fontFamily: getIt.get().isArabic() ? 'CairoArabic' : 'Poppins', fontSize: 8.f, color: AppColors.textColor, ), diff --git a/lib/presentation/vital_sign/vital_sign_page.dart b/lib/presentation/vital_sign/vital_sign_page.dart index f712aa77..fb3f1a58 100644 --- a/lib/presentation/vital_sign/vital_sign_page.dart +++ b/lib/presentation/vital_sign/vital_sign_page.dart @@ -236,7 +236,9 @@ class _VitalSignPageState extends State { fit: StackFit.expand, children: [ Image.asset( - AppAssets.bmiFullBody, + getIt.get().getAuthenticatedUser()?.gender == 1 + ? AppAssets.bmiFullBody + : AppAssets.bmiFullBodyFemale, fit: BoxFit.cover, alignment: Alignment.topCenter, ), diff --git a/lib/presentation/water_monitor/water_monitor_settings_page.dart b/lib/presentation/water_monitor/water_monitor_settings_page.dart index 5c47bc38..367cecc5 100644 --- a/lib/presentation/water_monitor/water_monitor_settings_page.dart +++ b/lib/presentation/water_monitor/water_monitor_settings_page.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.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/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -53,9 +54,7 @@ class _WaterMonitorSettingsPageState extends State { onChanged: (_) => onTap(), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), - (useUpperCase ? value.toUpperCase() : value.toCamelCase) - .toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1) - .expanded, + (useUpperCase ? value.toUpperCase() : value.toCamelCase).toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1).expanded, ], ).onPress(onTap), ); @@ -103,7 +102,7 @@ class _WaterMonitorSettingsPageState extends State { context: context, title: LocaleKeys.selectGender.tr(context: context), items: viewModel.genderOptions, - selectedValue: viewModel.selectedGender, + selectedValue: viewModel.selectedGenderDisplay, onSelected: viewModel.setGender, ); } @@ -112,8 +111,8 @@ class _WaterMonitorSettingsPageState extends State { _showSelectionBottomSheet( context: context, title: LocaleKeys.selectUnit.tr(context: context), - items: viewModel.heightUnits, - selectedValue: viewModel.selectedHeightUnit, + items: viewModel.getHeightUnitsList(), + selectedValue: viewModel.selectedHeightUnitDisplay, onSelected: viewModel.setHeightUnit, useUpperCase: true, ); @@ -123,8 +122,8 @@ class _WaterMonitorSettingsPageState extends State { _showSelectionBottomSheet( context: context, title: LocaleKeys.selectUnit.tr(context: context), - items: viewModel.weightUnits, - selectedValue: viewModel.selectedWeightUnit, + items: viewModel.getWeightUnitsList(), + selectedValue: viewModel.selectedWeightUnitDisplay, onSelected: viewModel.setWeightUnit, useUpperCase: true, ); @@ -135,7 +134,7 @@ class _WaterMonitorSettingsPageState extends State { context: context, title: LocaleKeys.selectActivityLevel.tr(context: context), items: viewModel.activityLevels, - selectedValue: viewModel.selectedActivityLevel, + selectedValue: viewModel.selectedActivityLevelDisplay, onSelected: viewModel.setActivityLevel, ); } @@ -145,7 +144,7 @@ class _WaterMonitorSettingsPageState extends State { context: context, title: LocaleKeys.selectNumberOfReminders.tr(context: context), items: viewModel.reminderOptions, - selectedValue: viewModel.selectedNumberOfReminders, + selectedValue: viewModel.selectedNumberOfRemindersDisplay, onSelected: viewModel.setNumberOfReminders, ); } @@ -184,6 +183,7 @@ class _WaterMonitorSettingsPageState extends State { VoidCallback? onRowTap, bool showDivider = true, }) { + AppState appState = getIt(); return Column( children: [ Row( @@ -231,7 +231,7 @@ class _WaterMonitorSettingsPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ - "Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor), + appState.isArabic() ? "الوحدة".toText12(isBold: true, color: AppColors.inputLabelTextColor) : "Unit".toText12(isBold: true, color: AppColors.inputLabelTextColor), unit.toText12(isBold: true, color: AppColors.textColor), ], ), @@ -307,7 +307,7 @@ class _WaterMonitorSettingsPageState extends State { _buildSettingsRow( icon: AppAssets.genderIcon, label: LocaleKeys.selectGender.tr(context: context), - value: viewModel.selectedGender, + value: viewModel.selectedGenderDisplay, onRowTap: () => _showGenderSelectionBottomsheet(context, viewModel), ), _buildSettingsRow( @@ -327,7 +327,7 @@ class _WaterMonitorSettingsPageState extends State { '175', keyboardType: TextInputType.number, ), - unit: viewModel.selectedHeightUnit, + unit: viewModel.selectedHeightUnitDisplay, onUnitTap: () => _showHeightUnitSelectionBottomSheet(context, viewModel), ), _buildSettingsRow( @@ -338,19 +338,19 @@ class _WaterMonitorSettingsPageState extends State { '75', keyboardType: TextInputType.number, ), - unit: viewModel.selectedWeightUnit, + unit: viewModel.selectedWeightUnitDisplay, onUnitTap: () => _showWeightUnitSelectionBottomsheet(context, viewModel), ), _buildSettingsRow( icon: AppAssets.dumbellIcon, label: LocaleKeys.activityLevel.tr(context: context), - value: viewModel.selectedActivityLevel, + value: viewModel.selectedActivityLevelDisplay, onRowTap: () => _showActivityLevelSelectionBottomsheet(context, viewModel), ), _buildSettingsRow( icon: AppAssets.notificationIconGrey, label: LocaleKeys.numberOfRemindersInADay.tr(context: context), - value: viewModel.selectedNumberOfReminders, + value: viewModel.selectedNumberOfRemindersDisplay, onRowTap: () => _showNumberOfRemindersSelectionBottomsheet(context, viewModel), showDivider: false, ), diff --git a/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart b/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart index 40018b61..058f14e8 100644 --- a/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart +++ b/lib/presentation/water_monitor/widgets/cup_bottomsheet_widgets.dart @@ -2,6 +2,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -17,13 +19,15 @@ import 'package:uuid/uuid.dart'; /// Bottom sheet to switch between existing cups or add new custom cup void showSwitchCupBottomSheet(BuildContext context) { + final isArabic = getIt.get().isArabic(); + return showCommonBottomSheetWithoutHeight( context, titleWidget: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Switch Cups".toText20(isBold: true), - "Select your preferred cup size".toText16(isBold: true, color: AppColors.greyTextColor), + (isArabic ? "تبديل الأكواب" : "Switch Cups").toText20(isBold: true), + (isArabic ? "اختر حجم الكوب المفضل لديك" : "Select your preferred cup size").toText16(isBold: true, color: AppColors.greyTextColor), ], ), child: SwitchCupBottomSheet(), diff --git a/lib/presentation/weather/weather_details_page.dart b/lib/presentation/weather/weather_details_page.dart new file mode 100644 index 00000000..6943b3ee --- /dev/null +++ b/lib/presentation/weather/weather_details_page.dart @@ -0,0 +1,237 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.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/weather/models/waether_cities_model.dart'; +import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/hmg_services/widgets/weather_widget.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:provider/provider.dart'; + +class WeatherDetailsPage extends StatelessWidget { + const WeatherDetailsPage({super.key}); + + Color _getColorFromColorName(ColorName? colorName) { + if (colorName == null) return AppColors.successColor; + + switch (colorName) { + case ColorName.GREEN: + return AppColors.successColor; + case ColorName.ORANGE: + return AppColors.warningColor; + case ColorName.RED: + return AppColors.errorColor; + } + } + + Color _getBackgroundColorFromColorName(ColorName? colorName) { + if (colorName == null) return AppColors.successLightColor.withValues(alpha: 0.1); + + switch (colorName) { + case ColorName.GREEN: + return AppColors.successLightColor.withValues(alpha: 0.1); + case ColorName.ORANGE: + return AppColors.warningLightColor.withValues(alpha: 0.1); + case ColorName.RED: + return AppColors.errorLightColor.withValues(alpha: 0.1); + } + } + + /// Get color based on category label + Color _getColorFromCategory(String? categoryLabel) { + if (categoryLabel == null) return AppColors.successColor; + + final category = categoryLabel.toLowerCase().trim(); + + // Green: Low, Beneficial + if (category.contains('low') || category.contains('beneficial')) { + return AppColors.successColor; + } + // Yellow: Fair + else if (category.contains('fair')) { + return AppColors.warningColorYellow; + } + // Red: Very High, At High Risk + else if (category.contains('very high') || category.contains('at high risk')) { + return AppColors.errorColor; + } + // Orange: Risk, High (but not "very high" or "at high risk") + else if (category.contains('risk') || category.contains('high')) { + return AppColors.warningColor; + } + + return AppColors.successColor; // Default to green + } + + /// Get background color based on category label + Color _getBackgroundFromCategory(String? categoryLabel) { + if (categoryLabel == null) return AppColors.successLightColor.withValues(alpha: 0.1); + + final category = categoryLabel.toLowerCase().trim(); + + // Green: Low, Beneficial + if (category.contains('low') || category.contains('beneficial')) { + return AppColors.successLightColor.withValues(alpha: 0.1); + } + // Yellow: Fair + else if (category.contains('fair')) { + return AppColors.warningColorYellow.withValues(alpha: 0.1); + } + // Red: Very High, At High Risk + else if (category.contains('very high') || category.contains('at high risk')) { + return AppColors.errorLightColor.withValues(alpha: 0.1); + } + // Orange: Risk, High (but not "very high" or "at high risk") + else if (category.contains('risk') || category.contains('high')) { + return AppColors.warningLightColor.withValues(alpha: 0.1); + } + + return AppColors.successLightColor.withValues(alpha: 0.1); // Default to green + } + + IconData _getIconFromCategory(String? category) { + if (category == null) return Icons.wb_sunny_outlined; + + final lowerCategory = category.toLowerCase(); + if (lowerCategory.contains('walking') || lowerCategory.contains('activity')) { + return Icons.directions_walk; + } else if (lowerCategory.contains('weather') || lowerCategory.contains('temperature')) { + return Icons.thermostat_outlined; + } else if (lowerCategory.contains('elderly') || lowerCategory.contains('hiking')) { + return Icons.elderly; + } + return Icons.wb_sunny_outlined; + } + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, weatherVM, child) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: LocaleKeys.healthWeatherIndicators.tr(), + isLeading: true, + child: weatherVM.isLoading + ? Center( + child: CircularProgressIndicator( + color: AppColors.primaryRedColor, + ), + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Reuse WeatherWidget at the top (without the button) + const WeatherWidget(showButton: false), + + SizedBox(height: 16.h), + + // Categories List (Sliders) + ...weatherVM.cityInfoList.map((cityInfo) { + return _buildCategoryCard( + description: cityInfo.name ?? '', + categoryLabel: cityInfo.category ?? '', + color: _getColorFromCategory(cityInfo.category), + backgroundColor: _getBackgroundFromCategory(cityInfo.category), + icon: _getIconFromCategory(cityInfo.category), + ); + }), + + SizedBox(height: 24.h), + ], + ), + ), + ); + }, + ); + } + + Widget _buildCategoryCard({ + required String description, + required String categoryLabel, + required Color color, + required Color backgroundColor, + required IconData icon, + }) { + return Container( + margin: EdgeInsets.only(bottom: 16.h), + padding: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 16.r, + hasShadow: false, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Icon + Container( + width: 40.w, + height: 40.w, + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(8.r), + ), + child: Icon( + icon, + color: color, + size: 24.w, + ), + ), + SizedBox(width: 12.w), + // Content + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + description.toText14( + isBold: true, + color: AppColors.textColor, + ), + SizedBox(height: 8.h), + // Progress bar + Container( + height: 4.h, + decoration: BoxDecoration( + color: AppColors.lightGrayBGColor, + borderRadius: BorderRadius.circular(2.r), + ), + child: FractionallySizedBox( + alignment: Alignment.centerLeft, + widthFactor: 0.7, + child: Container( + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(2.r), + ), + ), + ), + ), + ], + ), + ), + SizedBox(width: 12.w), + // Category Label + Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h), + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: BorderRadius.circular(8.r), + ), + child: categoryLabel.toText12( + color: color, + isBold: true, + maxLine: 1 + ), + ), + ], + ), + ).paddingSymmetrical(24.w, 0.w); + } +} + diff --git a/lib/splashPage.dart b/lib/splashPage.dart index 64518f30..7fe97e6f 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -5,6 +5,7 @@ import 'package:flutter_callkit_incoming/entities/call_event.dart'; import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; import 'package:flutter_zoom_videosdk/native/zoom_videosdk.dart'; import 'package:get_it/get_it.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_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; @@ -79,8 +80,9 @@ class _SplashScreenState extends State { } navigateToTeleConsult() async { + String roomID = await Utils.getStringFromPrefs(CacheConst.zoomRoomID); - print('RoomID: $roomID'); + String callTypeID = await Utils.getStringFromPrefs(CacheConst.callTypeID); // GetIt.instance().remove(key: CacheConst.isAppOpenedFromCall); Utils.removeFromPrefs(CacheConst.isAppOpenedFromCall); @@ -92,7 +94,7 @@ class _SplashScreenState extends State { GetIt.instance().navigatorKey.currentContext!, AppRoutes.zoomCallPage, // arguments: CallArguments(appointmentID, "111", "Patient", "40", "1", true, 1), - arguments: CallArguments(roomID, "123", "Patient", "40", "0", true, 1), + arguments: CallArguments(roomID, "123", "Patient", "40", "0", true, int.parse(callTypeID)), // arguments: CallArguments("SmallDailyStandup9875", "123", "Patient", "40", "0", false, int.parse(widget.incomingCallData!.appointmentNo!)), ); } @@ -114,6 +116,18 @@ class _SplashScreenState extends State { print('actionCallAccept Event Received: ------->'); FlutterCallkitIncoming.endAllCalls(); try { + // Get callTypeID and roomID from event's extra data + String? eventCallTypeID = event.body['extra']?['callTypeID']; + String? eventRoomID = event.body['extra']?['roomID']; + + // Re-save to SharedPreferences to ensure they're available + if (eventCallTypeID != null && eventCallTypeID.isNotEmpty) { + await Utils.saveStringFromPrefs(CacheConst.callTypeID, eventCallTypeID); + } + if (eventRoomID != null && eventRoomID.isNotEmpty) { + await Utils.saveStringFromPrefs(CacheConst.zoomRoomID, eventRoomID); + } + navigateToTeleConsult(); // String roomID = await Utils.getStringFromPrefs(SharedPrefsConsts.ZOOM_ROOM_ID); // String roomPass = await Utils.getStringFromPrefs(SharedPrefsConsts.ZOOM_ROOM_PASS); @@ -169,9 +183,22 @@ class _SplashScreenState extends State { } break; case Event.actionCallDecline: - print('actionCallConnected Event Received: ------->'); + print('actionCallDecline Event Received: ------->'); + + // Get session ID from cache + String sessionId = await Utils.getStringFromPrefs(CacheConst.zoomRoomID); + // + // // Update session status to 3 (declined by patient) + // if (sessionId.isNotEmpty) { + await _updateSessionStatus(sessionId, 3, 'Patient'); + // } + await FlutterCallkitIncoming.endAllCalls(); GetIt.instance().remove(key: CacheConst.isAppOpenedFromCall); + + // Clear cache + await Utils.saveStringFromPrefs(CacheConst.zoomRoomID, ''); + await Utils.saveStringFromPrefs(CacheConst.callTypeID, ''); break; case Event.actionCallEnded: break; @@ -207,20 +234,35 @@ class _SplashScreenState extends State { /// load the Privilege from service Future loadPrivilege() async { - // ProjectViewModel projectProvider = Provider.of(context, listen: false); - // projectProvider.setPrivilegeModelList(privilege: _privilegeService.privilegeModelList); - // projectProvider.setVidaPlusProjectList(_privilegeService.vidaPlusProjectListModel); - // projectProvider.setHMCProjectList(_privilegeService.hMCProjectListModel); - // projectProvider.setProjectsDetailList(_privilegeService.projectDetailListModel); - // double lat = await AppSharedPreferences().getDouble(USER_LAT) ?? 0.0; - // double long = await AppSharedPreferences().getDouble(USER_LONG) ?? 0.0; - // AppSharedPreferences().clear(); // Clearing Shared Preferences On App Launch - // await AppSharedPreferences().setDouble(USER_LAT, lat); - // await AppSharedPreferences().setDouble(USER_LONG, long); - // AppSharedPreferences().setString(APP_LANGUAGE, projectProvider.isArabic ? "ar" : "en"); - // var themeNotifier = Provider.of(context, listen: false); - // themeNotifier.setTheme(defaultTheme(fontName: projectProvider.isArabic ? 'Cairo' : 'Poppins')); - // PushNotificationHandler().init(context); // Asyncronously + // ...existing code... + } + + /// Update session status when call is declined + Future _updateSessionStatus(String sessionId, int sessionStatus, String sessionEndedBy) async { + try { + print('📡 Updating session status - SessionID: $sessionId, Status: $sessionStatus, EndedBy: $sessionEndedBy'); + + final apiClient = getIt.get(); + String sessionID = await Utils.getStringFromPrefs(CacheConst.zoomRoomID); + Map requestBody = { + "Open_SessionID": sessionID, + "SessionStatus": sessionStatus, + "SessionEndedBy": sessionEndedBy + }; + + await apiClient.post( + CHANGE_PATIENT_ER_SESSION, + body: requestBody, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + print('✅ Session status updated successfully: $response'); + }, + onFailure: (error, statusCode, {messageStatus, failureType}) { + print('❌ Failed to update session status: $error'); + }, + ); + } catch (e) { + print('❌ Exception updating session status: $e'); + } } @override diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index e9cd83eb..1e39eac8 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -90,6 +90,16 @@ class AppColors { static Color get myChildVaccineCardColor => isDarkMode ? dark.myChildVaccineCardColor : const Color(0xFFFF2D78); static Color get bookAppointment => isDarkMode ? dark.bookAppointment : const Color(0xFF415364); + // ── Health Calculators Colors ───────────────────────────────────────────── + static Color get bmiCalculatorColor => isDarkMode ? dark.bmiCalculatorColor : const Color(0xFFF43333); + static Color get caloriesCalculatorColor => isDarkMode ? dark.caloriesCalculatorColor : const Color(0xFF0B85F7); + static Color get bmrCalculatorColor => isDarkMode ? dark.bmrCalculatorColor : const Color(0xFF18C273); + static Color get idealBodyWeightColor => isDarkMode ? dark.idealBodyWeightColor : const Color(0xFFFF8012); + static Color get bodyFatCalculatorColor => isDarkMode ? dark.bodyFatCalculatorColor : const Color(0xFFFF5662); + static Color get carbsProteinFatColor => isDarkMode ? dark.carbsProteinFatColor : const Color(0xFF18C273); + static Color get ovulationPeriodColor => isDarkMode ? dark.ovulationPeriodColor : const Color(0xFFFF47A9); + static Color get deliveryDueDateColor => isDarkMode ? dark.deliveryDueDateColor : const Color(0xFFFF2D78); + // ── Water Monitor ───────────────────────────────────────────────────────── static Color get blueColor => isDarkMode ? dark.blueColor : const Color(0xFF4EB5FF); static Color get blueGradientColorOne => isDarkMode ? dark.blueGradientColorOne : const Color(0xFFF1F7FD); @@ -217,6 +227,16 @@ class AppColorsDark { Color get myChildVaccineCardColor => const Color(0xFFFF2D78); Color get bookAppointment => const Color(0xFF415364); + // Health Calculators + Color get bmiCalculatorColor => const Color(0xFFF43333); + Color get caloriesCalculatorColor => const Color(0xFF0B85F7); + Color get bmrCalculatorColor => const Color(0xFF18C273); + Color get idealBodyWeightColor => const Color(0xFFFF8012); + Color get bodyFatCalculatorColor => const Color(0xFFFF5662); + Color get carbsProteinFatColor => const Color(0xFF18C273); + Color get ovulationPeriodColor => const Color(0xFFFF47A9); + Color get deliveryDueDateColor => const Color(0xFFFF2D78); + // Water Monitor Color get blueColor => const Color(0xFF4EB5FF); Color get blueGradientColorOne => const Color(0xFF101C2A); diff --git a/lib/widgets/bottom_navigation/bottom_navigation.dart b/lib/widgets/bottom_navigation/bottom_navigation.dart index 2518375a..fe586fe4 100644 --- a/lib/widgets/bottom_navigation/bottom_navigation.dart +++ b/lib/widgets/bottom_navigation/bottom_navigation.dart @@ -27,7 +27,7 @@ class BottomNavigation extends StatelessWidget { BottomNavItem( icon: AppAssets.bookAppoBottom, fillIcon: AppAssets.bookAppoBottom, - label: LocaleKeys.appointment.tr(context: context), + label: LocaleKeys.appointments.tr(context: context), iconSize: 32, isSpecial: true), appState.isAuthenticated // ? BottomNavItem(icon: AppAssets.toDoBottom, label: LocaleKeys.todoList.tr(context: context)) @@ -80,7 +80,7 @@ class BottomNavigation extends StatelessWidget { ), // const SizedBox(height: 10), SizedBox(height: item.isSpecial ? 0 : 8.h), - item.label.toText11(isBold: true), + item.label.toText10(isBold: true), // SizedBox(height: item.isSpecial ? 5 : 0) ], ), diff --git a/lib/widgets/buttons/custom_button.dart b/lib/widgets/buttons/custom_button.dart index d2534587..d868755a 100644 --- a/lib/widgets/buttons/custom_button.dart +++ b/lib/widgets/buttons/custom_button.dart @@ -86,7 +86,8 @@ class CustomButton extends StatelessWidget { ), if (text.isNotEmpty && icon != null) Flexible( - child: Text( + child: + Text( text, overflow: textOverflow ?? TextOverflow.ellipsis, maxLines: 1, diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index d47971d7..069022d1 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -17,77 +17,92 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:permission_handler/permission_handler.dart'; class BottomSheetUtils { - showReminderBottomSheet(BuildContext context, DateTime dateTime, String doctorName, String eventId, String appoDateFormatted, String appoTimeFormatted, + Future showReminderBottomSheet(BuildContext context, DateTime dateTime, String doctorName, String eventId, String appoDateFormatted, String appoTimeFormatted, {required Function() onSuccess, String? title, String? description, Function(int)? onMultiDateSuccess, bool isMultiAllowed = false}) async { + // Check and request permissions based on platform + bool hasPermission = await _checkAndRequestCalendarPermissions(); + + if (hasPermission) { + // Permission granted, show the reminder selection bottom sheet + await _showReminderBottomSheet(context, dateTime, doctorName, eventId, appoDateFormatted, appoTimeFormatted, + onSuccess: onSuccess, title: title, description: description, onMultiDateSuccess: onMultiDateSuccess, isMultiAllowed: isMultiAllowed); + return true; + } else { + await _showPermissionDeniedDialog(); + return false; + } + } + + /// Checks and requests calendar permissions for both Android and iOS + /// Returns true if all required permissions are granted + Future _checkAndRequestCalendarPermissions() async { if (Platform.isAndroid) { - if (await PermissionService.isCalendarPermissionEnabled()) { - _showReminderBottomSheet(context, dateTime, doctorName, eventId, appoDateFormatted, appoTimeFormatted, - onSuccess: onSuccess, title: title, description: description, onMultiDateSuccess: onMultiDateSuccess, isMultiAllowed: isMultiAllowed); - } else { - showCommonBottomSheetWithoutHeight( - title: LocaleKeys.notice.tr(context: GetIt.instance().navigatorKey.currentContext!), - GetIt.instance().navigatorKey.currentContext!, - child: Utils.getWarningWidget( - loadingText: LocaleKeys.calendarPermissionAlert.tr(), - isShowActionButtons: true, - onCancelTap: () { - GetIt.instance().pop(); - }, - onConfirmTap: () async { - GetIt.instance().pop(); - openAppSettings(); - }), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); + // Android: Check existing permission first using PermissionService + bool hasPermission = await PermissionService.isCalendarPermissionEnabled(); + if (hasPermission) { + return true; } + + // Request calendarFullAccess permission (Android 14+ requires this) + // This internally requests both READ_CALENDAR and WRITE_CALENDAR + PermissionStatus status = await Permission.calendarFullAccess.request(); + + // If calendarFullAccess request didn't show dialog or failed, + // fallback to requesting individual permissions + if (!status.isGranted) { + Map statuses = await [ + Permission.calendarWriteOnly, + ].request(); + + return statuses[Permission.calendarWriteOnly]?.isGranted ?? false; + } + + return status.isGranted; } else { - if (await Permission.calendarWriteOnly.request().isGranted) { - if (await Permission.calendarFullAccess.request().isGranted) { - _showReminderBottomSheet(context, dateTime, doctorName, eventId, appoDateFormatted, appoTimeFormatted, - onSuccess: onSuccess, title: title, description: description, onMultiDateSuccess: onMultiDateSuccess, isMultiAllowed: isMultiAllowed); - } else { - showCommonBottomSheetWithoutHeight( - title: LocaleKeys.notice.tr(context: GetIt.instance().navigatorKey.currentContext!), - GetIt.instance().navigatorKey.currentContext!, - child: Utils.getWarningWidget( - loadingText: LocaleKeys.calendarPermissionAlert.tr(), - isShowActionButtons: true, - onCancelTap: () { - GetIt.instance().pop(); - }, - onConfirmTap: () async { - GetIt.instance().pop(); - openAppSettings(); - }), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - } - } else { - showCommonBottomSheetWithoutHeight( - title: LocaleKeys.notice.tr(context: GetIt.instance().navigatorKey.currentContext!), - GetIt.instance().navigatorKey.currentContext!, - child: Utils.getWarningWidget( - loadingText: LocaleKeys.calendarPermissionAlert.tr(), - isShowActionButtons: true, - onCancelTap: () { - GetIt.instance().pop(); - }, - onConfirmTap: () async { - GetIt.instance().pop(); - openAppSettings(); - }), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); + // iOS: Check if we already have full access + PermissionStatus fullAccessStatus = await Permission.calendarFullAccess.status; + + if (fullAccessStatus.isGranted) { + return true; } + + // Request write permission first (required before full access on iOS) + PermissionStatus writeStatus = await Permission.calendarWriteOnly.request(); + if (!writeStatus.isGranted) { + return false; + } + + // Then request full access permission + PermissionStatus fullStatus = await Permission.calendarFullAccess.request(); + return fullStatus.isGranted; } } + /// Shows permission denied dialog with option to open settings + Future _showPermissionDeniedDialog() async { + BuildContext? context = GetIt.instance().navigatorKey.currentContext; + if (context == null) return; + + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.notice.tr(context: context), + child: Utils.getWarningWidget( + loadingText: LocaleKeys.calendarPermissionAlert.tr(), + isShowActionButtons: true, + onCancelTap: () { + GetIt.instance().pop(); + }, + onConfirmTap: () async { + GetIt.instance().pop(); + await openAppSettings(); + }, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + Future _showReminderBottomSheet(BuildContext providedContext, DateTime dateTime, String doctorName, String eventId, String appoDateFormatted, String appoTimeFormatted, {required Function onSuccess, String? title, String? description, Function(int)? onMultiDateSuccess, bool? isMultiAllowed}) async { showCommonBottomSheetWithoutHeight(providedContext, title: LocaleKeys.setTimerOfReminder.tr(context: providedContext), child: PrescriptionReminderView( diff --git a/lib/widgets/date_range_selector/date_range_calender.dart b/lib/widgets/date_range_selector/date_range_calender.dart index fd37d0cb..463f516a 100644 --- a/lib/widgets/date_range_selector/date_range_calender.dart +++ b/lib/widgets/date_range_selector/date_range_calender.dart @@ -4,6 +4,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; @@ -12,31 +13,68 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; +import 'package:hmg_patient_app_new/widgets/date_range_selector/models/calendar_design_type.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:syncfusion_flutter_datepicker/datepicker.dart'; typedef OnRangeSelected = void Function(DateTime? start, DateTime? end); +typedef OnCalendarTypeChanged = void Function(bool isGregorian); class DateRangeSelector extends StatefulWidget { final OnRangeSelected onRangeSelected; + final CalendarDesignType designType; + final bool showCalendarToggle; + final OnCalendarTypeChanged? onCalendarTypeChanged; + final String? btnTitle; - const DateRangeSelector({super.key, required this.onRangeSelected}); + const DateRangeSelector({ + super.key, + required this.onRangeSelected, + this.designType = CalendarDesignType.defaultUI, + this.showCalendarToggle = true, + this.onCalendarTypeChanged, + this.btnTitle, + }); @override State createState() => _DateRangeSelectorState(); } +enum PickerViewMode { + date, + month, + year, +} + class _DateRangeSelectorState extends State { late DateRangePickerController _calendarController; DateTime? start; DateTime? end; late DateRangeSelectorRangeViewModel model; + PickerViewMode _viewMode = PickerViewMode.date; + + // Cache for Hijri conversions to avoid repeated calculations + final Map _hijriCache = {}; + + // Track the current Hijri month/year being displayed (for header display accuracy) + HijriGregDate? _currentHijriDisplay; + @override void initState() { _calendarController = DateRangePickerController(); scheduleMicrotask(() { - _calendarController.selectedRange = PickerDateRange(model.fromDate,model.toDate); + if (widget.designType == CalendarDesignType.designV2) { + // For V2, select today's date by default + final today = DateTime.now(); + _calendarController.selectedDate = today; + model.updateSelectedDate(today); + } else { + // For default UI, use range selection + _calendarController.selectedRange = PickerDateRange(model.fromDate, model.toDate); + } }); super.initState(); } @@ -45,9 +83,12 @@ class _DateRangeSelectorState extends State { Widget build(BuildContext context) { model = Provider.of(context); + _calendarController.selectedRange = PickerDateRange(model.fromDate, model.toDate); - _calendarController.selectedRange = PickerDateRange(model.fromDate,model.toDate); + return widget.designType == CalendarDesignType.designV2 ? _buildDesignV2(widget.btnTitle) : _buildDefaultUI(); + } + Widget _buildDefaultUI() { return Padding( padding: EdgeInsets.symmetric(horizontal: 0.h), child: Column( @@ -57,14 +98,8 @@ class _DateRangeSelectorState extends State { builder: (_, model, __) => selectionChip(model), ).paddingOnly(bottom: 16.h), Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: false, - ), - padding: EdgeInsets.all( - 16.h - ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false), + padding: EdgeInsets.all(16.h), child: Column( children: [ Row( @@ -72,13 +107,8 @@ class _DateRangeSelectorState extends State { fromDateComponent(), Text( LocaleKeys.to.tr(context: context), - style: TextStyle( - color: AppColors.calenderTextColor, - fontSize: 14.f, - fontWeight: FontWeight.w600, - letterSpacing: -.2 - ), - ).paddingSymmetrical(24.h,0.h), + style: TextStyle(color: AppColors.calenderTextColor, fontSize: 14.f, fontWeight: FontWeight.w600, letterSpacing: -.2), + ).paddingSymmetrical(24.h, 0.h), toDateComponent(), ], ), @@ -126,8 +156,7 @@ class _DateRangeSelectorState extends State { ), startRangeSelectionColor: AppColors.primaryRedColor, endRangeSelectionColor: AppColors.primaryRedColor, - rangeSelectionColor: - AppColors.primaryRedColor.withOpacity(0.1), + rangeSelectionColor: AppColors.primaryRedColor.withOpacity(0.1), rangeTextStyle: TextStyle( fontFamily: "Poppins", ), @@ -144,8 +173,7 @@ class _DateRangeSelectorState extends State { fontWeight: FontWeight.bold, ), ), - onSelectionChanged: - (DateRangePickerSelectionChangedArgs args) { + onSelectionChanged: (DateRangePickerSelectionChangedArgs args) { print("the value is ${args.value}"); if (args.value is PickerDateRange) { final PickerDateRange range = args.value; @@ -153,11 +181,10 @@ class _DateRangeSelectorState extends State { end = range.endDate; model.fromDate = start; model.toDate = end; - if(end == null) { - scheduleMicrotask((){ + if (end == null) { + scheduleMicrotask(() { model.resetCurrentlySelectedRange(); }); - } } }, @@ -169,7 +196,7 @@ class _DateRangeSelectorState extends State { Row( children: [ Consumer( - builder: (_, model, __) => Visibility( + builder: (_, model, __) => Visibility( visible: (model.fromDate != null || model.toDate != null), child: Expanded( child: Row( @@ -192,7 +219,9 @@ class _DateRangeSelectorState extends State { height: 56.h, ), ), - SizedBox(width: 16.h,) + SizedBox( + width: 16.h, + ) ], ), ), @@ -210,7 +239,6 @@ class _DateRangeSelectorState extends State { textColor: AppColors.textGreenColor, icon: AppAssets.reminder_bell, iconColor: AppColors.textGreenColor, - height: 56.h, ), ), @@ -221,11 +249,620 @@ class _DateRangeSelectorState extends State { ); } + Widget _buildDesignV2(String? btnTitle) { + return ChangeNotifierProvider( + create: (_) => DateRangCalenderModel(appState: getIt(), navigationService: getIt(), dialogService: getIt()), + child: Consumer( + builder: (context, calendarModel, child) { + // Check if current language is Arabic + final bool isArabic = context.locale.languageCode == 'ar'; + + return Padding( + padding: EdgeInsets.symmetric(horizontal: 0.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Conditionally show calendar toggle + if (widget.showCalendarToggle) + Selector( + selector: (_, model) => model.getSelectedTabIndex, + builder: (context, selectedIndex, child) => CustomTabBar( + activeBackgroundColor: AppColors.secondaryLightRedColor, + activeTextColor: AppColors.primaryRedColor, + initialIndex: selectedIndex, + tabs: [CustomTabBarModel(null, LocaleKeys.gregorianDate.tr()), CustomTabBarModel(null, LocaleKeys.hijriDate.tr())], + onTabChange: (index) { + final calendarModel = Provider.of(context, listen: false); + calendarModel.setTabIndex(index); + + // Notify parent widget about calendar type change (e.g., AuthenticationViewModel) + final isGregorian = index == 0; + widget.onCalendarTypeChanged?.call(isGregorian); + print('📅 Calendar type changed: ${isGregorian ? "Gregorian" : "Hijri"}'); + + // Reset view mode and Hijri display when switching calendar types + setState(() { + _viewMode = PickerViewMode.date; + _currentHijriDisplay = null; + }); + }, + ), + ), + if (widget.showCalendarToggle) + SizedBox( + height: 16.w, + ), + + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false), + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + // Show custom header for both calendar types + _buildCalendarHeader(isArabic, calendarModel), + // Wrap all views in SizedBox for consistent height + SizedBox( + height: 272.h, // Fixed height for calendar body only (no header) + child: Material( + color: AppColors.whiteColor, + // Rebuild when view mode or calendar type changes + key: ValueKey('${calendarModel.getSelectedTabIndex}-$_viewMode'), + child: _viewMode == PickerViewMode.month + ? _buildMonthPicker(isArabic, calendarModel) + : _viewMode == PickerViewMode.year + ? _buildYearPicker(isArabic, calendarModel) + : (calendarModel.isHijri ? _buildHijriCalendar(isArabic) : _buildGregorianCalendar(isArabic)), + ), + ), + ], + ), + ), + // Only Select button for V2 (no cancel button) + CustomButton( + text: btnTitle ?? LocaleKeys.select.tr(context: context), + onPressed: () { + // Don't reset calendar type - keep user's preference + Navigator.of(context).pop(); + // For single date selection, pass the same date as both start and end + widget.onRangeSelected(model.fromDate, model.fromDate); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: Colors.transparent, + textColor: AppColors.whiteColor, + height: 56.h, + ).paddingOnly(top: 24.h), + // Bottom image from ui2.png + SizedBox(height: 16.h), + ], + ), + ); + }, + ), + ); + } + + // Build Gregorian calendar (Design V2 - Single Date Selection) + Widget _buildGregorianCalendar(bool isArabic) { + return SfDateRangePicker( + controller: _calendarController, + selectionMode: DateRangePickerSelectionMode.single, + showNavigationArrow: false, + headerHeight: 0, + // Hide built-in header, use custom header + backgroundColor: AppColors.whiteColor, + monthViewSettings: DateRangePickerMonthViewSettings( + viewHeaderStyle: DateRangePickerViewHeaderStyle( + backgroundColor: AppColors.whiteColor, + textStyle: TextStyle( + fontSize: 12.f, + fontWeight: FontWeight.w600, + letterSpacing: -0.46, + color: AppColors.textColor, + ), + ), + showTrailingAndLeadingDates: false, + dayFormat: "EEE", + ), + // Custom cell builder for square border selection + cellBuilder: (BuildContext context, DateRangePickerCellDetails cellDetails) { + if (cellDetails.date != DateTime(0)) { + final isToday = cellDetails.date.day == DateTime.now().day && cellDetails.date.month == DateTime.now().month && cellDetails.date.year == DateTime.now().year; + final isSelected = _calendarController.selectedDate != null && + cellDetails.date.day == _calendarController.selectedDate!.day && + cellDetails.date.month == _calendarController.selectedDate!.month && + cellDetails.date.year == _calendarController.selectedDate!.year; + + return Container( + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.h), + border: isSelected ? Border.all(color: AppColors.primaryRedColor, width: 2) : null, + color: Colors.transparent, + ), + child: Text( + cellDetails.date.day.toString(), + style: TextStyle( + fontFamily: "Poppins", + fontSize: 12.f, + color: AppColors.textColor, + fontWeight: isToday ? FontWeight.bold : (isSelected ? FontWeight.w600 : FontWeight.normal), + ), + ), + ); + } + return Container(); + }, + selectionShape: DateRangePickerSelectionShape.rectangle, + selectionRadius: 8.h, + selectionColor: Colors.transparent, + selectionTextStyle: TextStyle( + fontFamily: "Poppins", + color: AppColors.textColor, + fontWeight: FontWeight.w600, + ), + todayHighlightColor: Colors.transparent, + monthCellStyle: DateRangePickerMonthCellStyle( + textStyle: TextStyle( + fontFamily: "Poppins", + fontSize: 12.f, + color: AppColors.textColor, + ), + todayTextStyle: TextStyle( + fontFamily: "Poppins", + color: AppColors.textColor, + fontWeight: FontWeight.bold, + ), + ), + onViewChanged: (DateRangePickerViewChangedArgs args) { + // Trigger rebuild when month changes + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() {}); + } + }); + }, + onSelectionChanged: (DateRangePickerSelectionChangedArgs args) { + if (args.value is DateTime) { + setState(() { + start = args.value; + end = args.value; + }); + model.updateSelectedDate(args.value); + } + }, + ); + } + + // Build Hijri calendar (Design V2 - Single Date Selection) + // Note: SfDateRangePicker doesn't have built-in Hijri support + // Using Gregorian calendar with Hijri date conversion in the model + Widget _buildHijriCalendar(bool isArabic) { + final calendarModel = Provider.of(context, listen: false); + + return SfDateRangePicker( + controller: _calendarController, + selectionMode: DateRangePickerSelectionMode.single, + showNavigationArrow: false, + headerHeight: 0, + backgroundColor: AppColors.whiteColor, + monthViewSettings: DateRangePickerMonthViewSettings( + viewHeaderStyle: DateRangePickerViewHeaderStyle( + backgroundColor: AppColors.whiteColor, + textStyle: TextStyle( + fontSize: 12.f, + fontWeight: FontWeight.w600, + letterSpacing: -0.46, + color: AppColors.textColor, + ), + ), + showTrailingAndLeadingDates: false, + dayFormat: "EEE", + ), + cellBuilder: (BuildContext context, DateRangePickerCellDetails cellDetails) { + if (cellDetails.date != DateTime(0)) { + // Use cached Hijri conversion + final dateKey = '${cellDetails.date.year}-${cellDetails.date.month}-${cellDetails.date.day}'; + final hijriDate = _hijriCache.putIfAbsent(dateKey, () => calendarModel.gregorianToHijri(cellDetails.date)); + + final isToday = cellDetails.date.day == DateTime.now().day && cellDetails.date.month == DateTime.now().month && cellDetails.date.year == DateTime.now().year; + final isSelected = _calendarController.selectedDate != null && + cellDetails.date.day == _calendarController.selectedDate!.day && + cellDetails.date.month == _calendarController.selectedDate!.month && + cellDetails.date.year == _calendarController.selectedDate!.year; + + return Container( + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.h), + border: isSelected ? Border.all(color: AppColors.primaryRedColor, width: 2) : null, + color: Colors.transparent, + ), + child: Text( + hijriDate.day.toString(), + style: TextStyle( + fontFamily: "Poppins", + fontSize: 12.f, + color: AppColors.textColor, + fontWeight: isToday ? FontWeight.bold : (isSelected ? FontWeight.w600 : FontWeight.normal), + ), + ), + ); + } + return Container(); + }, + selectionShape: DateRangePickerSelectionShape.rectangle, + selectionRadius: 8.h, + selectionColor: Colors.transparent, + selectionTextStyle: TextStyle( + fontFamily: "Poppins", + color: AppColors.textColor, + fontWeight: FontWeight.w600, + ), + todayHighlightColor: Colors.transparent, + monthCellStyle: DateRangePickerMonthCellStyle( + textStyle: TextStyle( + fontFamily: "Poppins", + fontSize: 12.f, + color: AppColors.textColor, + ), + todayTextStyle: TextStyle( + fontFamily: "Poppins", + color: AppColors.textColor, + fontWeight: FontWeight.bold, + ), + ), + onViewChanged: (DateRangePickerViewChangedArgs args) { + // Clear cache when month changes to avoid stale data + _hijriCache.clear(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() {}); + } + }); + }, + onSelectionChanged: (DateRangePickerSelectionChangedArgs args) { + if (args.value is DateTime) { + setState(() { + start = args.value; + end = args.value; + }); + model.updateSelectedDate(args.value); + } + }, + ); + } + + // Build unified calendar header for both Gregorian and Hijri + Widget _buildCalendarHeader(bool isArabic, DateRangCalenderModel calendarModel) { + final displayedDate = _calendarController.displayDate ?? DateTime.now(); + + String monthText; + String yearText; + + if (calendarModel.isHijri) { + // Use tracked Hijri display date if available, otherwise convert from displayed Gregorian date + final hijriDate = _currentHijriDisplay ?? calendarModel.gregorianToHijri(displayedDate); + final monthName = calendarModel.getMonthNameLocalized(hijriDate.month, isArabic); + monthText = monthName; + yearText = hijriDate.year.toString(); + } else { + final monthNames = isArabic + ? ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'] + : ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; + monthText = monthNames[displayedDate.month - 1]; + yearText = displayedDate.year.toString(); + } + + return Padding( + padding: EdgeInsets.only(bottom: 8.h), + child: SizedBox( + height: 40.h, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + // Month text - clickable + GestureDetector( + onTap: () { + setState(() { + _viewMode = _viewMode == PickerViewMode.month ? PickerViewMode.date : PickerViewMode.month; + }); + }, + child: Text( + monthText, + style: TextStyle( + fontSize: 18.f, + fontWeight: FontWeight.w600, + letterSpacing: -0.46, + color: AppColors.primaryRedColor, + fontFamily: "Poppins", + ), + textAlign: isArabic ? TextAlign.right : TextAlign.left, + ), + ), + SizedBox(width: 8.h), + // Year text - clickable + GestureDetector( + onTap: () { + setState(() { + _viewMode = _viewMode == PickerViewMode.year ? PickerViewMode.date : PickerViewMode.year; + }); + }, + child: Text( + yearText, + style: TextStyle( + fontSize: 18.f, + fontWeight: FontWeight.w600, + letterSpacing: -0.46, + color: AppColors.primaryRedColor, + fontFamily: "Poppins", + ), + textAlign: isArabic ? TextAlign.right : TextAlign.left, + ), + ), + ], + ), + Row( + children: [ + IconButton( + icon: Icon(Icons.chevron_left, color: AppColors.primaryRedColor), + onPressed: () { + if (_viewMode == PickerViewMode.date) { + _calendarController.backward!(); + // Update tracked Hijri display when navigating months + if (calendarModel.isHijri) { + setState(() { + _currentHijriDisplay = null; // Will recalculate on next build + }); + } + } else if (_viewMode == PickerViewMode.year) { + // Navigate years backward by 12 + final currentDate = _calendarController.displayDate ?? DateTime.now(); + if (calendarModel.isHijri) { + // For Hijri, convert back and forth + final hijriDate = _currentHijriDisplay ?? calendarModel.gregorianToHijri(currentDate); + try { + final newHijriDate = HijriGregDate(day: 1, month: hijriDate.month, year: hijriDate.year - 12); + final newGregorianDate = calendarModel.hijriToGregorian(newHijriDate); + _calendarController.displayDate = DateTime(newGregorianDate.year, newGregorianDate.month, 1); + _currentHijriDisplay = newHijriDate; + } catch (e) { + // Fallback + _calendarController.displayDate = DateTime(currentDate.year - 10, currentDate.month, 1); + } + } else { + _calendarController.displayDate = DateTime(currentDate.year - 12, currentDate.month, 1); + } + setState(() {}); + } + }, + padding: EdgeInsets.zero, + constraints: BoxConstraints(), + ), + SizedBox(width: 8.h), + IconButton( + icon: Icon(Icons.chevron_right, color: AppColors.primaryRedColor), + onPressed: () { + if (_viewMode == PickerViewMode.date) { + _calendarController.forward!(); + // Update tracked Hijri display when navigating months + if (calendarModel.isHijri) { + setState(() { + _currentHijriDisplay = null; // Will recalculate on next build + }); + } + } else if (_viewMode == PickerViewMode.year) { + // Navigate years forward by 12 + final currentDate = _calendarController.displayDate ?? DateTime.now(); + if (calendarModel.isHijri) { + // For Hijri, convert back and forth + final hijriDate = _currentHijriDisplay ?? calendarModel.gregorianToHijri(currentDate); + try { + final newHijriDate = HijriGregDate(day: 1, month: hijriDate.month, year: hijriDate.year + 12); + final newGregorianDate = calendarModel.hijriToGregorian(newHijriDate); + _calendarController.displayDate = DateTime(newGregorianDate.year, newGregorianDate.month, 1); + _currentHijriDisplay = newHijriDate; + } catch (e) { + // Fallback + _calendarController.displayDate = DateTime(currentDate.year + 10, currentDate.month, 1); + } + } else { + _calendarController.displayDate = DateTime(currentDate.year + 12, currentDate.month, 1); + } + setState(() {}); + } + }, + padding: EdgeInsets.zero, + constraints: BoxConstraints(), + ), + ], + ), + ], + ), + ), + ); + } + + // Build month picker grid + Widget _buildMonthPicker(bool isArabic, DateRangCalenderModel calendarModel) { + final displayedDate = _calendarController.displayDate ?? DateTime.now(); + final currentDate = DateTime.now(); + + int currentMonth; + int currentYear; + int displayedYear; + + if (calendarModel.isHijri) { + // For Hijri calendar + final hijriCurrent = calendarModel.gregorianToHijri(currentDate); + final hijriDisplayed = calendarModel.gregorianToHijri(displayedDate); + currentMonth = hijriCurrent.month; + currentYear = hijriCurrent.year; + displayedYear = hijriDisplayed.year; + } else { + // For Gregorian calendar + currentMonth = currentDate.month; + currentYear = currentDate.year; + displayedYear = displayedDate.year; + } + + List monthNames; + if (calendarModel.isHijri) { + monthNames = isArabic ? DateRangCalenderModel.hijriMonthNamesArabic : DateRangCalenderModel.hijriMonthNamesEnglish; + } else { + monthNames = isArabic ? DateRangCalenderModel.gregorianMonthNamesArabic : DateRangCalenderModel.gregorianMonthNamesEnglish; + } + + return GridView.builder( + padding: EdgeInsets.all(8.h), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, childAspectRatio: 2.5, crossAxisSpacing: 8.h, mainAxisSpacing: 8.h), + itemCount: 12, + itemBuilder: (context, index) { + final monthIndex = index + 1; + final isCurrentMonth = currentMonth == monthIndex && currentYear == displayedYear; + + return GestureDetector( + onTap: () { + // Update the displayed month based on calendar type + if (calendarModel.isHijri) { + // Convert Hijri month to Gregorian date + try { + final hijriDate = HijriGregDate(day: 1, month: monthIndex, year: displayedYear); + final gregorianDate = calendarModel.hijriToGregorian(hijriDate); + _calendarController.displayDate = DateTime(gregorianDate.year, gregorianDate.month, 1); + // Track the Hijri month/year for accurate header display + _currentHijriDisplay = hijriDate; + // Clear cache since we changed the month + _hijriCache.clear(); + } catch (e) { + // Fallback if conversion fails + _calendarController.displayDate = DateTime(displayedDate.year, monthIndex, 1); + } + } else { + // Gregorian month - direct update + _calendarController.displayDate = DateTime(displayedYear, monthIndex, 1); + } + + // Switch back to date view + setState(() { + _viewMode = PickerViewMode.date; + }); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.h), + border: isCurrentMonth ? Border.all(color: AppColors.primaryRedColor, width: 2) : null, + color: Colors.transparent, + ), + child: Center( + child: Text( + monthNames[index], + style: TextStyle( + fontFamily: "Poppins", + fontSize: 14.f, + color: AppColors.textColor, + fontWeight: isCurrentMonth ? FontWeight.w600 : FontWeight.normal, + ), + textAlign: TextAlign.center, + ), + ), + ), + ); + }, + ); + } + + // Build year picker grid + Widget _buildYearPicker(bool isArabic, DateRangCalenderModel calendarModel) { + final displayedDate = _calendarController.displayDate ?? DateTime.now(); + final currentYear = DateTime.now().year; + + int displayedYear; + int currentDisplayYear; + int startYear; + + if (calendarModel.isHijri) { + // For Hijri calendar + final hijriDisplayed = calendarModel.gregorianToHijri(displayedDate); + final hijriCurrent = calendarModel.gregorianToHijri(DateTime.now()); + displayedYear = hijriDisplayed.year; + currentDisplayYear = hijriCurrent.year; + startYear = (displayedYear ~/ 12) * 12; + } else { + // For Gregorian calendar + displayedYear = displayedDate.year; + currentDisplayYear = currentYear; + startYear = (displayedYear ~/ 12) * 12; + } + + return GridView.builder( + padding: EdgeInsets.all(8.h), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + childAspectRatio: 2.5, + crossAxisSpacing: 8.h, + mainAxisSpacing: 8.h, + ), + itemCount: 12, + itemBuilder: (context, index) { + final year = startYear + index; + final isCurrentYear = currentDisplayYear == year; + + return GestureDetector( + onTap: () { + // Update the displayed year based on calendar type + if (calendarModel.isHijri) { + // Convert Hijri year to Gregorian date + try { + final currentHijriMonth = _currentHijriDisplay?.month ?? calendarModel.gregorianToHijri(displayedDate).month; + final hijriDate = HijriGregDate(day: 1, month: currentHijriMonth, year: year); + final gregorianDate = calendarModel.hijriToGregorian(hijriDate); + _calendarController.displayDate = DateTime(gregorianDate.year, gregorianDate.month, 1); + // Track the Hijri month/year for accurate header display + _currentHijriDisplay = hijriDate; + } catch (e) { + // Fallback if conversion fails + _calendarController.displayDate = DateTime(displayedDate.year, displayedDate.month, 1); + } + } else { + // Gregorian year - direct update + _calendarController.displayDate = DateTime(year, displayedDate.month, 1); + } + + // Switch back to date view + setState(() { + _viewMode = PickerViewMode.date; + }); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.h), + border: isCurrentYear ? Border.all(color: AppColors.primaryRedColor, width: 2) : null, + color: Colors.transparent, + ), + child: Center( + child: Text( + year.toString(), + style: TextStyle( + fontFamily: "Poppins", + fontSize: 14.f, + color: AppColors.textColor, + fontWeight: isCurrentYear ? FontWeight.w600 : FontWeight.normal, + ), + textAlign: TextAlign.center, + ), + ), + ), + ); + }, + ); + } + fromDateComponent() { return Consumer( builder: (_, model, __) { - return displayDate(LocaleKeys.startDate.tr(context: context), - model.getDateString(model.fromDate), model.fromDate == null); + return displayDate(LocaleKeys.startDate.tr(context: context), model.getDateString(model.fromDate), model.fromDate == null); }, ); } @@ -233,21 +870,16 @@ class _DateRangeSelectorState extends State { toDateComponent() { return Consumer( builder: (_, model, __) { - return displayDate(LocaleKeys.endDate.tr(context: context), - model.getDateString(model.toDate), model.toDate == null); + return displayDate(LocaleKeys.endDate.tr(context: context), model.getDateString(model.toDate), model.toDate == null); }, ); } displayDate(String label, String? date, bool isNotSelected) => Expanded( - child: Row( + child: Row( spacing: 12.h, children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.rangeCalendar, - iconColor: isNotSelected ? AppColors.borderOnlyColor: AppColors.blackColor , - height: 24, - width: 24), + Utils.buildSvgWithAssets(icon: AppAssets.rangeCalendar, iconColor: isNotSelected ? AppColors.borderOnlyColor : AppColors.blackColor, height: 24, width: 24), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -271,7 +903,7 @@ class _DateRangeSelectorState extends State { ) ], ), - ); + ); selectionChip(DateRangeSelectorRangeViewModel model) { return Row( @@ -279,59 +911,46 @@ class _DateRangeSelectorState extends State { children: [ AppCustomChipWidget( labelText: LocaleKeys.thisWeek.tr(context: context), - backgroundColor: model.currentlySelectedRange == Range.WEEKLY - ? AppColors.primaryRedColor.withOpacity(0.1) - : AppColors.whiteColor, + backgroundColor: model.currentlySelectedRange == Range.WEEKLY ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor, shape: RoundedRectangleBorder( side: BorderSide( - color: model.currentlySelectedRange == Range.WEEKLY - ? AppColors.primaryRedBorderColor - : AppColors.chipBorderColorOpacity20, + color: model.currentlySelectedRange == Range.WEEKLY ? AppColors.primaryRedBorderColor : AppColors.chipBorderColorOpacity20, width: 1, ), borderRadius: BorderRadius.circular(10)), - ).onPress((){ + ).onPress(() { _calendarController.selectedRange = null; model.currentlySelectedRange = Range.WEEKLY; model.calculateDatesFromRange(); }), AppCustomChipWidget( labelText: LocaleKeys.lastMonth.tr(context: context), - backgroundColor: model.currentlySelectedRange == Range.LAST_MONTH - ? AppColors.primaryRedColor.withOpacity(0.1) - : AppColors.whiteColor, + backgroundColor: model.currentlySelectedRange == Range.LAST_MONTH ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor, shape: RoundedRectangleBorder( side: BorderSide( - color: model.currentlySelectedRange == Range.LAST_MONTH - ? AppColors.primaryRedBorderColor - : AppColors.chipBorderColorOpacity20, + color: model.currentlySelectedRange == Range.LAST_MONTH ? AppColors.primaryRedBorderColor : AppColors.chipBorderColorOpacity20, width: 1, ), borderRadius: BorderRadius.circular(10)), - ).onPress((){ + ).onPress(() { _calendarController.selectedRange = null; model.currentlySelectedRange = Range.LAST_MONTH; model.calculateDatesFromRange(); }), AppCustomChipWidget( labelText: LocaleKeys.lastSixMonths.tr(context: context), - backgroundColor: model.currentlySelectedRange == Range.LAST_6MONTH - ? AppColors.primaryRedColor.withOpacity(0.1) - : AppColors.whiteColor, + backgroundColor: model.currentlySelectedRange == Range.LAST_6MONTH ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor, shape: RoundedRectangleBorder( side: BorderSide( - color: model.currentlySelectedRange == Range.LAST_6MONTH - ? AppColors.primaryRedBorderColor - : AppColors.chipBorderColorOpacity20, + color: model.currentlySelectedRange == Range.LAST_6MONTH ? AppColors.primaryRedBorderColor : AppColors.chipBorderColorOpacity20, width: 1, ), borderRadius: BorderRadius.circular(10)), isEnglishOnly: true, - ).onPress((){ + ).onPress(() { _calendarController.selectedRange = null; model.currentlySelectedRange = Range.LAST_6MONTH; model.calculateDatesFromRange(); - }), AppCustomChipWidget( richText: Row( @@ -341,23 +960,18 @@ class _DateRangeSelectorState extends State { ], ), labelText: "${LocaleKeys.year.tr(context: context)} ${model.getCurrentYear}", - backgroundColor: model.currentlySelectedRange == Range.THIS_YEAR - ? AppColors.primaryRedColor.withOpacity(0.1) - : AppColors.whiteColor, + backgroundColor: model.currentlySelectedRange == Range.THIS_YEAR ? AppColors.primaryRedColor.withOpacity(0.1) : AppColors.whiteColor, shape: RoundedRectangleBorder( side: BorderSide( - color: model.currentlySelectedRange == Range.THIS_YEAR - ? AppColors.primaryRedBorderColor - : AppColors.chipBorderColorOpacity20, + color: model.currentlySelectedRange == Range.THIS_YEAR ? AppColors.primaryRedBorderColor : AppColors.chipBorderColorOpacity20, width: 1, ), borderRadius: BorderRadius.circular(10)), isEnglishOnly: true, - ).onPress((){ + ).onPress(() { _calendarController.selectedRange = null; model.currentlySelectedRange = Range.THIS_YEAR; model.calculateDatesFromRange(); - }), ], ); diff --git a/lib/widgets/date_range_selector/models/calendar_design_type.dart b/lib/widgets/date_range_selector/models/calendar_design_type.dart new file mode 100644 index 00000000..5cbc8129 --- /dev/null +++ b/lib/widgets/date_range_selector/models/calendar_design_type.dart @@ -0,0 +1,5 @@ +enum CalendarDesignType { + defaultUI, + designV2, +} + diff --git a/lib/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart b/lib/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart new file mode 100644 index 00000000..54c23a72 --- /dev/null +++ b/lib/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart @@ -0,0 +1,555 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/services/dialog_service.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +// ==================== Hijri Date Classes ==================== + +/// Represents a Hijri date with day, month, and year components. +class HijriGregDate { + final int day; + final int month; + final int year; + + HijriGregDate({required this.day, required this.month, required this.year}) + : assert(day >= 1 && day <= 30, 'Day must be between 1 and 30'), + assert(month >= 1 && month <= 12, 'Month must be between 1 and 12'), + assert(year > 0, 'Year must be positive'); + + @override + String toString() => '$day/$month/$year H'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + return other is HijriGregDate && other.day == day && other.month == month && other.year == year; + } + + @override + int get hashCode => day.hashCode ^ month.hashCode ^ year.hashCode; +} + +/// Utility class for converting between Hijri and Gregorian calendars. +class HijriGregConverter { + static const int _hijriEpoch = 1948439; + + static HijriGregDate gregorianToHijri(DateTime gregorianDate) { + int julianDay = _gregorianToJulian(gregorianDate); + return _julianToHijri(julianDay); + } + + static DateTime hijriToGregorian(HijriGregDate hijriDate) { + int julianDay = _hijriToJulian(hijriDate); + return _julianToGregorian(julianDay); + } + + static int _gregorianToJulian(DateTime date) { + int year = date.year; + int month = date.month; + int day = date.day; + + if (month <= 2) { + year--; + month += 12; + } + + int a = year ~/ 100; + int b = 2 - a + (a ~/ 4); + + return (365.25 * (year + 4716)).floor() + (30.6001 * (month + 1)).floor() + day + b - 1524; + } + + static DateTime _julianToGregorian(int julianDay) { + int a = julianDay + 32044; + int b = (4 * a + 3) ~/ 146097; + int c = a - (146097 * b) ~/ 4; + int d = (4 * c + 3) ~/ 1461; + int e = c - (1461 * d) ~/ 4; + int m = (5 * e + 2) ~/ 153; + + int day = e - (153 * m + 2) ~/ 5 + 1; + int month = m + 3 - 12 * (m ~/ 10); + int year = 100 * b + d - 4800 + m ~/ 10; + + return DateTime(year, month, day); + } + + static HijriGregDate _julianToHijri(int julianDay) { + int daysSinceEpoch = julianDay - _hijriEpoch; + int hijriYear = (daysSinceEpoch * 33 ~/ 10631) + 1; + + int yearStartJulian = _hijriYearStartJulian(hijriYear); + while (yearStartJulian > julianDay) { + hijriYear--; + yearStartJulian = _hijriYearStartJulian(hijriYear); + } + while (yearStartJulian + _hijriYearLength(hijriYear) <= julianDay) { + hijriYear++; + yearStartJulian = _hijriYearStartJulian(hijriYear); + } + + int dayOfYear = julianDay - yearStartJulian + 1; + int month = 1; + int dayInMonth = dayOfYear; + + while (month <= 12) { + int monthLength = _hijriMonthLength(hijriYear, month); + if (dayInMonth <= monthLength) { + break; + } + dayInMonth -= monthLength; + month++; + } + + if (dayInMonth < 1) dayInMonth = 1; + if (dayInMonth > 30) dayInMonth = 30; + + return HijriGregDate(day: dayInMonth, month: month, year: hijriYear); + } + + static int _hijriToJulian(HijriGregDate hijriDate) { + int yearStart = _hijriYearStartJulian(hijriDate.year); + int dayOfYear = 0; + + for (int i = 1; i < hijriDate.month; i++) { + dayOfYear += _hijriMonthLength(hijriDate.year, i); + } + + dayOfYear += hijriDate.day - 1; + return yearStart + dayOfYear; + } + + static int _hijriYearStartJulian(int hijriYear) { + if (hijriYear <= 1) return _hijriEpoch; + + int totalDays = 0; + for (int year = 1; year < hijriYear; year++) { + totalDays += _hijriYearLength(year); + } + + return _hijriEpoch + totalDays; + } + + static int _hijriYearLength(int year) { + return _isHijriLeapYear(year) ? 355 : 354; + } + + static int _hijriMonthLength(int year, int month) { + if (month <= 0 || month > 12) return 30; + + const List monthLengths = [30, 29, 30, 29, 30, 29, 30, 29, 30, 29, 30, 29]; + int length = monthLengths[month - 1]; + + if (month == 12 && _isHijriLeapYear(year)) { + length = 30; + } + + return length; + } + + static bool _isHijriLeapYear(int year) { + if (year <= 0) return false; + const leapYears = [2, 5, 7, 10, 13, 16, 18, 21, 24, 26, 29]; + int yearInCycle = ((year - 1) % 30) + 1; + return leapYears.contains(yearInCycle); + } + + static int getHijriMonthLength(int year, int month) { + return _hijriMonthLength(year, month); + } + + static bool isHijriLeapYear(int year) { + return _isHijriLeapYear(year); + } +} + +// ==================== Main Model ==================== + +enum CalendarType { + gregorian, + hijri, +} + +class DateRangCalenderModel extends ChangeNotifier { + final AppState appState; + final ErrorHandlerService navigationService; + final DialogService dialogService; + + // Static variable to persist calendar type preference across instances + static CalendarType _persistedCalendarType = CalendarType.gregorian; + static int _persistedTabIndex = 0; + + DateRangCalenderModel({required this.appState, required this.navigationService, required this.dialogService}) { + // Initialize from persisted state + _initializeFromPersistedState(); + } + + int _selectedTabIndex = 0; + CalendarType _calendarType = CalendarType.gregorian; + + void _initializeFromPersistedState() { + // Load from static persisted values + _selectedTabIndex = _persistedTabIndex; + _calendarType = _persistedCalendarType; + } + + // Umm al-Qura calendar adjustment (in days) + // This offset corrects the astronomical calculation to match the official Saudi calendar + static const int _ummAlQuraOffset = -1; + + // Hijri month names in English (short form) + static const List _hijriMonthNamesShort = [ + 'Muh', + 'Saf', + 'Rab I', + 'Rab II', + 'Jum I', + 'Jum II', + 'Raj', + 'Sha', + 'Ram', + 'Shaw', + 'Dhu Q', + 'Dhu H', + ]; + + // Hijri month names in English (full form) - public for month picker + static const List hijriMonthNamesEnglish = [ + 'Muharram', + 'Safar', + 'Rabi\' I', + 'Rabi\' II', + 'Jumada I', + 'Jumada II', + 'Rajab', + 'Sha\'ban', + 'Ramadan', + 'Shawwal', + 'Dhu al-Qi\'dah', + 'Dhu al-Hijjah', + ]; + + // Hijri month names in Arabic (short form) + static const List _hijriMonthNamesShortArabic = [ + 'محرم', + 'صفر', + 'ربيع الأول', + 'ربيع الثاني', + 'جمادى الأولى', + 'جمادى الثانية', + 'رجب', + 'شعبان', + 'رمضان', + 'شوال', + 'ذو القعدة', + 'ذو الحجة', + ]; + + // Hijri month names in Arabic (full form) - public for month picker + static const List hijriMonthNamesArabic = [ + 'محرم', + 'صفر', + 'ربيع الأول', + 'ربيع الثاني', + 'جمادى الأولى', + 'جمادى الثانية', + 'رجب', + 'شعبان', + 'رمضان', + 'شوال', + 'ذو القعدة', + 'ذو الحجة', + ]; + + // Gregorian month names (short form) + static const List _gregorianMonthNames = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec', + ]; + + // Gregorian month names in English (full form) - public for month picker + static const List gregorianMonthNamesEnglish = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December', + ]; + + // Gregorian month names in Arabic + static const List _gregorianMonthNamesArabic = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', + ]; + + // Gregorian month names in Arabic (full form) - public for month picker + static const List gregorianMonthNamesArabic = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر', + ]; + + // Weekday names in English (short form) + static const List _weekdayNamesEnglish = [ + 'Sun', + 'Mon', + 'Tue', + 'Wed', + 'Thu', + 'Fri', + 'Sat', + ]; + + // Weekday names in Arabic (short form) + static const List _weekdayNamesArabic = [ + 'أحد', + 'اثنين', + 'ثلاثاء', + 'أربعاء', + 'خميس', + 'جمعة', + 'سبت', + ]; + + int get getSelectedTabIndex => _selectedTabIndex; + + CalendarType get calendarType => _calendarType; + + set selectedTabIndex(int value) { + _selectedTabIndex = value; + _calendarType = value == 0 ? CalendarType.gregorian : CalendarType.hijri; + notifyListeners(); + } + + void setTabIndex(int index) { + _selectedTabIndex = index; + _calendarType = index == 0 ? CalendarType.gregorian : CalendarType.hijri; + // Persist the selection for next time the widget opens + _persistCalendarTypePreference(); + notifyListeners(); + } + + void _persistCalendarTypePreference() { + // Store the preference in static variables so it persists when widget reopens + _persistedTabIndex = _selectedTabIndex; + _persistedCalendarType = _calendarType; + } + + bool get isGregorian => _calendarType == CalendarType.gregorian; + + bool get isHijri => _calendarType == CalendarType.hijri; + + /// Reset to Gregorian and clear persisted preference + /// Call this only when explicitly needed (e.g., logout) + void reset({bool force = false}) { + if (force) { + // Force reset - clear both instance and persisted state + _selectedTabIndex = 0; + _calendarType = CalendarType.gregorian; + _persistedTabIndex = 0; + _persistedCalendarType = CalendarType.gregorian; + notifyListeners(); + } + // If not forcing, keep the user's preference (persisted state remains) + } + + /// Clear persisted calendar type preference (reset to Gregorian on next open) + static void clearPersistedPreference() { + _persistedTabIndex = 0; + _persistedCalendarType = CalendarType.gregorian; + } + + // ==================== Conversion Methods ==================== + + /// Converts Gregorian DateTime to Hijri date with Umm al-Qura correction + HijriGregDate gregorianToHijri(DateTime gregorianDate) { + // Apply Umm al-Qura offset for accurate Saudi calendar + final adjustedDate = gregorianDate.add(Duration(days: _ummAlQuraOffset)); + return HijriGregConverter.gregorianToHijri(adjustedDate); + } + + /// Converts Hijri date to Gregorian DateTime with Umm al-Qura correction + DateTime hijriToGregorian(HijriGregDate hijriDate) { + final gregorianDate = HijriGregConverter.hijriToGregorian(hijriDate); + // Reverse the Umm al-Qura offset + return gregorianDate.subtract(Duration(days: _ummAlQuraOffset)); + } + + // ==================== Date Formatting Methods ==================== + + /// Formats a date based on the current calendar type + /// Returns formatted string like "14 Apr,26" for Gregorian or "14 Muh,1447" for Hijri + String formatDate(DateTime? date) { + if (date == null) return "-"; + + if (isGregorian) { + return formatGregorianDate(date); + } else { + return formatHijriDate(date); + } + } + + /// Formats a Gregorian date + /// Format: "dd MMM,yy" (e.g., "14 Apr,26") + String formatGregorianDate(DateTime date) { + String year = date.year.toString().substring(2); + String month = _gregorianMonthNames[date.month - 1]; + String day = date.day.toString(); + return '$day $month,$year'; + } + + /// Formats a date as Hijri + /// Format: "dd MMM,yyyy" (e.g., "14 Muh,1447") + String formatHijriDate(DateTime gregorianDate) { + HijriGregDate hijriDate = gregorianToHijri(gregorianDate); + String month = _hijriMonthNamesShort[hijriDate.month - 1]; + return '${hijriDate.day} $month,${hijriDate.year}'; + } + + /// Gets month name based on current calendar type + String getMonthName(int month) { + if (isGregorian) { + return _gregorianMonthNames[month - 1]; + } else { + return _hijriMonthNamesShort[month - 1]; + } + } + + /// Gets month name in Arabic based on current calendar type + String getMonthNameArabic(int month) { + if (isGregorian) { + return _gregorianMonthNamesArabic[month - 1]; + } else { + return _hijriMonthNamesShortArabic[month - 1]; + } + } + + /// Gets month name with language support + String getMonthNameLocalized(int month, bool isArabic) { + return isArabic ? getMonthNameArabic(month) : getMonthName(month); + } + + /// Gets weekday name in English + String getWeekdayName(int weekday) { + // weekday: 1 = Monday, 7 = Sunday in DateTime + // Convert to Sunday = 0 index + int index = weekday % 7; + return _weekdayNamesEnglish[index]; + } + + /// Gets weekday name in Arabic + String getWeekdayNameArabic(int weekday) { + // weekday: 1 = Monday, 7 = Sunday in DateTime + // Convert to Sunday = 0 index + int index = weekday % 7; + return _weekdayNamesArabic[index]; + } + + /// Gets weekday name with language support + String getWeekdayNameLocalized(int weekday, bool isArabic) { + return isArabic ? getWeekdayNameArabic(weekday) : getWeekdayName(weekday); + } + + /// Converts a date string to display format based on calendar type + String formatDateString(String? dateString) { + if (dateString == null || dateString.isEmpty) return "-"; + + try { + DateTime date = DateTime.parse(dateString); + return formatDate(date); + } catch (e) { + return "-"; + } + } + + // ==================== Date Range Methods ==================== + + /// Formats a date range based on current calendar type + /// Returns formatted string like "14 Apr,26 - 20 Apr,26" for Gregorian + String formatDateRange(DateTime? startDate, DateTime? endDate) { + if (startDate == null || endDate == null) return "-"; + + String start = formatDate(startDate); + String end = formatDate(endDate); + return '$start - $end'; + } + + /// Gets the current date in the selected calendar format + String getCurrentDateFormatted() { + return formatDate(DateTime.now()); + } + + // ==================== Calendar Type Specific Helpers ==================== + + /// Gets the year based on calendar type + int getCurrentYear() { + if (isGregorian) { + return DateTime.now().year; + } else { + return gregorianToHijri(DateTime.now()).year; + } + } + + /// Gets formatted year string based on calendar type + String getFormattedYear(DateTime date) { + if (isGregorian) { + return date.year.toString(); + } else { + return gregorianToHijri(date).year.toString(); + } + } + + /// Checks if a year is leap year based on calendar type + bool isLeapYear(int year) { + if (isGregorian) { + return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); + } else { + return HijriGregConverter.isHijriLeapYear(year); + } + } + + /// Gets the number of days in a month based on calendar type + int getDaysInMonth(int year, int month) { + if (isGregorian) { + return DateTime(year, month + 1, 0).day; + } else { + return HijriGregConverter.getHijriMonthLength(year, month); + } + } +} diff --git a/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart b/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart index 3f67b6a6..e92d0817 100644 --- a/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart +++ b/lib/widgets/date_range_selector/viewmodel/date_range_view_model.dart @@ -85,4 +85,12 @@ class DateRangeSelectorRangeViewModel extends ChangeNotifier { notifyListeners(); } + /// Update both fromDate and toDate (for single date selection in V2) + void updateSelectedDate(DateTime? date) { + _fromDate = date; + _toDate = date; + _currentlySelectedRange = null; + notifyListeners(); + } + } diff --git a/lib/widgets/family_files/family_file_add_widget.dart b/lib/widgets/family_files/family_file_add_widget.dart index ff19fa28..92a14fd8 100644 --- a/lib/widgets/family_files/family_file_add_widget.dart +++ b/lib/widgets/family_files/family_file_add_widget.dart @@ -89,7 +89,7 @@ class _FamilyFileAddWidgetState extends State { ), SizedBox(height: 20.h), CustomButton( - text: LocaleKeys.pleaseVerify.tr(context: context), + text: LocaleKeys.add.tr(context: context), onPressed: () { FocusScope.of(context).unfocus(); if (ValidationUtils.isValidatedIdAndPhoneWithCountryValidation( diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index a134ac79..9242cfcd 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -1,6 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; @@ -9,9 +9,14 @@ 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/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +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/time_picker_widget.dart'; +import 'dart:ui' as ui; class TextInputWidget extends StatelessWidget { final String labelText; @@ -40,6 +45,7 @@ class TextInputWidget extends StatelessWidget { final Widget? suffix; final Color? labelColor; final Function(String)? onSubmitted; + final String? btnTitle; // new multiline options final bool isMultiline; @@ -54,45 +60,45 @@ class TextInputWidget extends StatelessWidget { // final List countryList; // final Function(Country)? onCountryChange; - TextInputWidget({ - super.key, - required this.labelText, - required this.hintText, - this.controller, - this.onChange, - this.onCalendarTypeChanged, - this.prefix, - this.isEnable = true, - this.isBorderAllowed = true, - this.isAllowRadius = true, - this.isReadOnly = false, - this.keyboardType = TextInputType.number, - this.focusNode, - this.autoFocus = false, - this.padding, - this.isAllowLeadingIcon = false, - this.leadingIcon, - this.isCountryDropDown = false, - this.hasError = false, - this.errorMessage, - this.onCountryChange, - this.selectionType, - this.fontSize, - this.isWalletAmountInput = false, - this.suffix, - this.labelColor, - this.onSubmitted, - this.hintColor, - // multiline defaults - this.isMultiline = false, - this.minLines = 3, - this.maxLines = 6, - this.isHideSwitcher, - this.isArrowTrailing, - this.fontFamily, - // this.countryList = const [], - // this.onCountryChange, - }); + TextInputWidget( + {super.key, + required this.labelText, + required this.hintText, + this.controller, + this.onChange, + this.onCalendarTypeChanged, + this.prefix, + this.isEnable = true, + this.isBorderAllowed = true, + this.isAllowRadius = true, + this.isReadOnly = false, + this.keyboardType = TextInputType.number, + this.focusNode, + this.autoFocus = false, + this.padding, + this.isAllowLeadingIcon = false, + this.leadingIcon, + this.isCountryDropDown = false, + this.hasError = false, + this.errorMessage, + this.onCountryChange, + this.selectionType, + this.fontSize, + this.isWalletAmountInput = false, + this.suffix, + this.labelColor, + this.onSubmitted, + this.hintColor, + // multiline defaults + this.isMultiline = false, + this.minLines = 3, + this.maxLines = 6, + this.isHideSwitcher, + this.isArrowTrailing, + this.fontFamily, + // this.countryList = const [], + // this.onCountryChange, + this.btnTitle}); final FocusNode _focusNode = FocusNode(); @@ -213,7 +219,6 @@ class TextInputWidget extends StatelessWidget { } Widget _buildTrailingIcon(BuildContext context, {bool isArrowTrailing = false}) { - final AppState appState = getIt.get(); return Container( height: 40.h, width: 40.w, @@ -222,26 +227,26 @@ class TextInputWidget extends StatelessWidget { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 12.r, color: AppColors.whiteColor), child: GestureDetector( onTap: () async { - bool isGregorian = true; - final picked = await showHijriGregBottomSheet(context, - isShowTimeSlots: true, - switcherIcon: Utils.buildSvgWithAssets(icon: AppAssets.language, width: 24.h, height: 24.h), - language: appState.getLanguageCode()!, - initialDate: DateTime.now(), - showCalendarToggle: isHideSwitcher == true ? false : true, - // fontFamily: appState.getLanguageCode() == "ar" ? "GESSTwo" : "Poppins", - fontFamily: "Poppins", - okWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)), - cancelWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)), - onCalendarTypeChanged: (bool value) { - isGregorian = value; - }); - if (picked != null && onChange != null) { - if (onCalendarTypeChanged != null) { - onCalendarTypeChanged!.call(isGregorian); - } - onChange!(picked.toString()); - } + showCommonBottomSheetWithoutHeight( + title: LocaleKeys.selectDate.tr(context: context), + context, + child: DateRangeSelector( + onRangeSelected: (DateTime? start, DateTime? end) { + var picked = start.toString().split(" ").first; + if (picked != null && onChange != null) { + onChange!(picked.toString()); + } + }, + designType: CalendarDesignType.designV2, + showCalendarToggle: isHideSwitcher == true ? false : true, + onCalendarTypeChanged: onCalendarTypeChanged, + btnTitle: btnTitle ?? LocaleKeys.done.tr()), + isFullScreen: false, + isCloseButtonVisible: true, + callBackFunc: () { + // Don't reset calendar type - persist user's preference + }, + ); }, child: Utils.buildSvgWithAssets(icon: isArrowTrailing ? AppAssets.arrow_down : AppAssets.calendar), ), @@ -269,10 +274,10 @@ class TextInputWidget extends StatelessWidget { final picked = await TimePickerWidget.show( context, initialTime: initialTime, - use24HourFormat: false, // You can make this configurable if needed + use24HourFormat: true, onTimeSelected: (time) { if (onChange != null) { - final formattedTime = TimePickerWidget.formatTime(time); + final formattedTime = TimePickerWidget.formatTime(time, use24HourFormat: true); onChange!(formattedTime); } }, @@ -280,7 +285,7 @@ class TextInputWidget extends StatelessWidget { // Update controller if time was picked if (picked != null && controller != null) { - controller!.text = TimePickerWidget.formatTime(picked); + controller!.text = TimePickerWidget.formatTime(picked, use24HourFormat: true); } }, child: Utils.buildSvgWithAssets(icon: isArrowTrailing ? AppAssets.arrow_down : AppAssets.alarm_clock_icon), @@ -299,7 +304,7 @@ class TextInputWidget extends StatelessWidget { return Builder( builder: (context) { return Directionality( - textDirection: isArabic ? TextDirection.rtl : TextDirection.ltr, + textDirection: isArabic ? ui.TextDirection.rtl : ui.TextDirection.ltr, child: Localizations.override( context: context, locale: const Locale('en', 'US'), // Force English locale for TextField @@ -312,7 +317,7 @@ class TextInputWidget extends StatelessWidget { readOnly: isReadOnly, textAlignVertical: TextAlignVertical.top, textAlign: isArabic ? TextAlign.right : TextAlign.left, - textDirection: TextDirection.ltr, + textDirection: ui.TextDirection.ltr, onChanged: onChange, focusNode: focusNode ?? _focusNode, autofocus: autoFocus, @@ -353,7 +358,7 @@ class TextInputWidget extends StatelessWidget { ), prefixIconConstraints: BoxConstraints(minWidth: 30.h), prefixIcon: prefix == null ? null : "+${prefix!}".toText14(letterSpacing: -1, color: AppColors.textColor, isBold: true), - contentPadding: EdgeInsets.zero, + contentPadding: EdgeInsets.only(right: isArabic ? 10.w : 0), border: InputBorder.none, focusedBorder: InputBorder.none, enabledBorder: InputBorder.none, @@ -481,4 +486,3 @@ class _ThousandSeparatorInputFormatter extends TextInputFormatter { ); } } - diff --git a/lib/widgets/language_switcher.dart b/lib/widgets/language_switcher.dart index 3ee10df3..15237363 100644 --- a/lib/widgets/language_switcher.dart +++ b/lib/widgets/language_switcher.dart @@ -56,8 +56,7 @@ class _LanguageSelectorState extends State { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.language), - SizedBox(width: 6.h), + Text( // currentLangData['name']?.toUpperCase() ?? 'EN', widget.currentLanguage == 'ar' ? "ENGLISH" : "العربية", @@ -69,6 +68,9 @@ class _LanguageSelectorState extends State { isLanguageSwitcher: true, fontFamily: widget.currentLanguage == 'ar' ? "Poppins" : "CairoArabic"), ), + SizedBox(width: 6.h), + Utils.buildSvgWithAssets(icon: AppAssets.language), + ], ), ), diff --git a/lib/widgets/map/map_utility_screen.dart b/lib/widgets/map/map_utility_screen.dart index a24317e6..300ad57d 100644 --- a/lib/widgets/map/map_utility_screen.dart +++ b/lib/widgets/map/map_utility_screen.dart @@ -176,7 +176,8 @@ class MapUtilityScreen extends StatelessWidget { width: MediaQuery.sizeOf(context).width, child: TextInputWidget( labelText: LocaleKeys.enterPickupLocationManually.tr(context: context), - hintText: LocaleKeys.enterPickupLocation.tr(context: context), + hintText: "" , + // hintText: LocaleKeys.enterPickupLocation.tr(context: context) , controller: TextEditingController( text: vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description, ), @@ -210,7 +211,9 @@ class MapUtilityScreen extends StatelessWidget { context, child: SizedBox( height: MediaQuery.sizeOf(context).height * .8, - child: LocationInputBottomSheet(), + child: LocationInputBottomSheet( + moveCameraController:(location) => context.read().moveController(location) , + ), ), isFullScreen: false, isCloseButtonVisible: true, diff --git a/lib/widgets/order_tracking/request_tracking.dart b/lib/widgets/order_tracking/request_tracking.dart index f07c0b19..519a41c2 100644 --- a/lib/widgets/order_tracking/request_tracking.dart +++ b/lib/widgets/order_tracking/request_tracking.dart @@ -26,12 +26,7 @@ class OrderTrackingWidget extends StatelessWidget { Widget? getBarAndDot(OrderTrackingState? state, BuildContext context, double width) { - print("the parent padding value is $parentPaddingValue"); - print("the parent padding value is ${(parentPaddingValue+24)}"); - print("the width - (parentPaddingValue+24) ${(width - (parentPaddingValue+24))}"); double widthOfOneState = (width/4) - (parentPaddingValue+8.h); - print("te width of one state is $widthOfOneState"); - print("the value of${width/4}"); if(state == null) { return Row( children: List.generate(4, (index) { diff --git a/lib/widgets/stepper/stepper_widget.dart b/lib/widgets/stepper/stepper_widget.dart index 5d901ac8..9946c1ae 100644 --- a/lib/widgets/stepper/stepper_widget.dart +++ b/lib/widgets/stepper/stepper_widget.dart @@ -12,10 +12,11 @@ class StepperWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return oneProgressBar(width, activeColor, hasThumb); + return oneProgressBar(context, width, activeColor, hasThumb); } - Widget oneProgressBar(double width, Color color, bool hasThumb) { + Widget oneProgressBar(BuildContext context, double width, Color color, bool hasThumb) { + final isRtl = Directionality.of(context) == TextDirection.rtl; return Row( children: [ AnimatedSize( @@ -42,8 +43,9 @@ class StepperWidget extends StatelessWidget { Visibility( visible: hasThumb, child: Positioned( - top: -6.h, // move thumb above bar center - left: width - 22.h, // move to right end + top: -6.h, + left: isRtl ? 4.h : null, + right: isRtl ? null : 4.h, child: thumb(color), ), ) diff --git a/lib/widgets/time_picker_widget.dart b/lib/widgets/time_picker_widget.dart index fd5cb409..eadbf5f3 100644 --- a/lib/widgets/time_picker_widget.dart +++ b/lib/widgets/time_picker_widget.dart @@ -1,3 +1,5 @@ +import 'dart:ui' as ui; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -126,48 +128,50 @@ class _TimePickerBottomSheetState extends State<_TimePickerBottomSheet> { @override Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - color: AppColors.whiteColor, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20.r), - topRight: Radius.circular(20.r), + return Directionality( + textDirection: ui.TextDirection.ltr, + child: Container( + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20.r), + topRight: Radius.circular(20.r), + ), ), - ), - child: SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Header - Container( - padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide( - color: AppColors.dividerColor, - width: 1, + child: SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Header + Container( + padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 16.h), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: AppColors.dividerColor, + width: 1, + ), ), ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.selectTime.tr(context: context).toText18( - weight: FontWeight.w600, - color: AppColors.textColor, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.selectTime.tr(context: context).toText18( + weight: FontWeight.w600, + color: AppColors.textColor, + ), + GestureDetector( + onTap: () => Navigator.pop(context), + child: Utils.buildSvgWithAssets( + icon: AppAssets.cancel, + width: 24.h, + height: 24.h, + iconColor: AppColors.textColor, ), - GestureDetector( - onTap: () => Navigator.pop(context), - child: Utils.buildSvgWithAssets( - icon: AppAssets.cancel, - width: 24.h, - height: 24.h, - iconColor: AppColors.textColor, ), - ), - ], + ], + ), ), - ), // Time Picker SizedBox( @@ -345,6 +349,7 @@ class _TimePickerBottomSheetState extends State<_TimePickerBottomSheet> { ], ), ), + ), ); } } diff --git a/lib/widgets/user_avatar_widget.dart b/lib/widgets/user_avatar_widget.dart index 6132401c..3271adf4 100644 --- a/lib/widgets/user_avatar_widget.dart +++ b/lib/widgets/user_avatar_widget.dart @@ -14,6 +14,7 @@ class UserAvatarWidget extends StatelessWidget { final bool isCircular; final double? borderRadius; final String? customProfileImageData; // For family members or other users + final bool isFamilyMember; // New flag to prevent AppState fallback const UserAvatarWidget({ Key? key, @@ -25,6 +26,7 @@ class UserAvatarWidget extends StatelessWidget { this.isCircular = false, this.borderRadius, this.customProfileImageData, + this.isFamilyMember = false, // Default to false for backward compatibility }) : super(key: key); @override @@ -33,9 +35,17 @@ class UserAvatarWidget extends StatelessWidget { final userGender = gender ?? appState.getAuthenticatedUser()?.gender ?? 1; final userAge = age ?? appState.getAuthenticatedUser()?.age ?? 0; - // Use custom profile image data if provided, otherwise use AppState data - final profileImageData = customProfileImageData ?? appState.getProfileImageData; - + // Determine which profile image data to use: + // - For family members: ONLY use customProfileImageData (no AppState fallback) + // - For main user: Use customProfileImageData if provided, otherwise AppState + final String? profileImageData; + if (isFamilyMember) { + // Family member mode: only use their specific image data, no fallback + profileImageData = customProfileImageData; + } else { + // Main user mode: use custom data or fall back to AppState + profileImageData = customProfileImageData ?? appState.getProfileImageData; + } // Determine the default image based on gender and age final String defaultImage; diff --git a/pubspec.lock b/pubspec.lock index d46cbda7..f8ccd5ee 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -515,6 +515,54 @@ packages: url: "https://pub.dev" source: hosted version: "0.20.5" + flutter_image_compress: + dependency: "direct main" + description: + name: flutter_image_compress + sha256: "51d23be39efc2185e72e290042a0da41aed70b14ef97db362a6b5368d0523b27" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + flutter_image_compress_common: + dependency: transitive + description: + name: flutter_image_compress_common + sha256: c5c5d50c15e97dd7dc72ff96bd7077b9f791932f2076c5c5b6c43f2c88607bfb + url: "https://pub.dev" + source: hosted + version: "1.0.6" + flutter_image_compress_macos: + dependency: transitive + description: + name: flutter_image_compress_macos + sha256: "20019719b71b743aba0ef874ed29c50747461e5e8438980dfa5c2031898f7337" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + flutter_image_compress_ohos: + dependency: transitive + description: + name: flutter_image_compress_ohos + sha256: e76b92bbc830ee08f5b05962fc78a532011fcd2041f620b5400a593e96da3f51 + url: "https://pub.dev" + source: hosted + version: "0.0.3" + flutter_image_compress_platform_interface: + dependency: transitive + description: + name: flutter_image_compress_platform_interface + sha256: "579cb3947fd4309103afe6442a01ca01e1e6f93dc53bb4cbd090e8ce34a41889" + url: "https://pub.dev" + source: hosted + version: "1.0.5" + flutter_image_compress_web: + dependency: transitive + description: + name: flutter_image_compress_web + sha256: b9b141ac7c686a2ce7bb9a98176321e1182c9074650e47bb140741a44b6f5a96 + url: "https://pub.dev" + source: hosted + version: "0.1.5" flutter_inappwebview: dependency: "direct main" description: @@ -675,10 +723,9 @@ packages: flutter_swiper_view: dependency: "direct main" description: - name: flutter_swiper_view - sha256: "2a165b259e8a4c49d4da5626b967ed42a73dac2d075bd9e266ad8d23b9f01879" - url: "https://pub.dev" - source: hosted + path: "package/flutter_swiper_view" + relative: true + source: path version: "1.1.8" flutter_test: dependency: "direct dev" diff --git a/pubspec.yaml b/pubspec.yaml index 7f14f57c..4d853184 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -71,7 +71,8 @@ dependencies: firebase_analytics: ^11.5.1 firebase_crashlytics: ^4.3.8 jiffy: ^6.4.3 - hijri_gregorian_calendar: ^0.1.1 +# hijri_gregorian_calendar: +# path: /Users/aamir/StudioProjects/hijri_gregorian_calendar sms_otp_auto_verify: ^2.2.0 web: any flutter_staggered_animations: ^1.1.1