From 3bf5ca98d50c1f8d1173c6decbf3f47a5b990732 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 23 Aug 2026 15:25:38 +0300 Subject: [PATCH 01/15] Livecare Terms & conditions, PayTabs Tamara fixes --- assets/langs/ar-SA.json | 83 ++++++- assets/langs/en-US.json | 83 ++++++- .../immediate_livecare_view_model.dart | 9 + lib/features/payfort/payfort_view_model.dart | 2 +- lib/features/paytabs/paytabs_view_model.dart | 50 +++- lib/generated/locale_keys.g.dart | 81 ++++++ .../appointment_payment_page.dart | 30 ++- .../book_appointment_page.dart | 1 + .../immediate_livecare_payment_details.dart | 50 +++- .../livecare/livecare_terms_conditions.dart | 230 ++++++++++++++++++ lib/presentation/home/landing_page.dart | 1 + .../ancillary_order_payment_page.dart | 107 ++++++++ .../ancillary_procedures_details_page.dart | 3 + lib/presentation/todo_section/todo_page.dart | 3 +- 14 files changed, 719 insertions(+), 14 deletions(-) create mode 100644 lib/presentation/book_appointment/livecare/livecare_terms_conditions.dart diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index c025a030..911856ab 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1969,5 +1969,86 @@ "hide": "إخفاء", "viewPatientQR": "عرض رمز الاستجابة السريعة للمريض", "patientQR": "رمز الاستجابة السريعة للمريض", - "forText": "عن" + "forText": "عن", + "liveCareTermsHeading": "شروط خدمات الرعاية الصحية عن بُعد", + "liveCareTermsIntroHeading": "مقدمة", + "liveCareTermsConditions1": "تعتبر شركة مجموعة الدكتور سليمان الحبيب للخدمات الطبية ('المجموعة') واحدة من الشركات الرائدة في قطاع الرعاية الصحية في المملكة العربية السعودية والتي تسعى دائمًا لتحسين خدماتها في المنطقة، وكخطوة لنقل خدماتنا إلى مستوى آخر ولتسهيل عملية تلقي خدمات الرعاية الصحية، فقد اعتمدنا نهج تقديم بعض خدماتنا عن بُعد للمرضى. تنطبق هذه الشروط على خدمات الرعاية الصحية عن بُعد التي نقدمها، والتي تتيح إجراء الاستشارات بين الطبيب والمريض عبر الإنترنت، باستخدام تقنية الاتصالات الصوتية أو المرئية (يشار إليها غالباً باسم 'خدمات الرعاية الصحية عن بُعد').", + "liveCareTermsConditions2": "الغرض من خدمات الرعاية الصحية عن بعد هو تسهيل الاتصالات بين الممارسين الصحيين والمرضى لتقديم خدمات الرعاية الصحية من خلال وسائل الاتصال الإلكترونية المتقدمة، والتي تتيح للممارسين الصحيين تقييم وتشخيص وعلاج ومتابعة المرضى عن بُعد دون الحاجة إلى حضور المرضى شخصياً.", + "liveCareTermsHeading2": "الاتفاقية", + "liveCareTermsConditions3": "يتم تقديم خدمات الرعاية الصحية عن بُعد عبر تطبيق الهاتف المحمول أو الموقع الإلكتروني للمجموعة. تنطبق الشروط التالية على خدمات الرعاية الصحية عن بُعد: ", + "liveCareTermsConditions3a": "شروط استخدام الموقع الالكتروني - ", + "liveCareTermsConditions3b": "تفاقية مستخدم التطبيق - ", + "liveCareTermsConditions3c": "روط استخدام الخدمات الإلكترونية - ", + "liveCareTermsConditions3d": "شروط خدمات الرعاية الصحية عن بُعد هذه - ", + "liveCareTermsConditions4": "بالموافقة على الاستمرار في خدمات الرعاية الصحية عن بُعد وقبول هذه الشروط، فإنك تدخل في اتفاقية مع المجموعة تتضمن جميع الشروط المذكورة أعلاه (اتفاقية خدمات الرعاية الصحية عن بُعد).", + "liveCareTermsConditions5": "يتعين عليك أيضاً قراءة سياسة الخصوصية الخاصة بنا حيث تحتوي سياسة الخصوصية على تفاصيل حول كيفية استخدامنا لمعلوماتك الشخصية وحقوقك بشأنها. من خلال متابعة استخدام خدمات الرعاية الصحية عن بُعد، فإنك توافق على استخدامنا لمعلوماتك الشخصية وفقاً لسياسة الخصوصية الخاصة بنا وتوافق صراحةً على استخدامنا لمعلوماتك الشخصية لأداء خدمات الرعاية الصحية عن بُعد وأداء واجباتنا المهنية كمقدم لخدمات الرعاية الطبية.", + "liveCareTermsConditions6": "يتعين عليك أيضاً قراءة سياسة الخصوصية الخاصة بنا حيث تحتوي سياسة الخصوصية على تفاصيل حول كيفية استخدامنا لمعلوماتك الشخصية وحقوقك بشأنها. من خلال متابعة استخدام خدمات الرعاية الصحية عن بُعد، فإنك توافق على استخدامنا لمعلوماتك الشخصية وفقاً لسياسة الخصوصية الخاصة بنا وتوافق صراحةً على استخدامنا لمعلوماتك الشخصية لأداء خدمات الرعاية الصحية عن بُعد وأداء واجباتنا المهنية كمقدم لخدمات الرعاية الطبية.", + "liveCareTermsHeading3": "1.\tالتعاريف", + "liveCareTermsConditions7": "المجموعة تعني شركة مجموعة الدكتور سليمان الحبيب للخدمات الطبية والشركات التابعة لها.", + "liveCareTermsConditions8": "خدمات الرعاية الصحية عن بُعد تعني خدمة الرعاية الصحية عن بُعد التي تقدمها المجموعة، والتي تتيح الاستشارة والتشخيص والمتابعة عن بُعد بين المريض والطبيب، باستخدام التطبيق و/أو موقع الويب.", + "liveCareTermsConditions9": "خدماتنا تعني بعض الخدمات التي تقدمها المجموعة والتي تغطي استعراض السجلات الطبية للمرضى ونتائج الفحوصات الطبية، وتوفير التشخيصات الطبية بالإضافة إلى توفر خدمة الرعاية الصحية عن بعد بالصوت والفيديو بين ممارسي الرعاية الصحية والمرضى.", + "liveCareTermsConditions10": "التطبيق يعني تطبيق (Dr. ALHABIB) وهو عبارة عن منصة يتم من خلالها تقديم خدماتنا ويمكن للمستخدمين الولوج إليها.", + "liveCareTermsConditions11": "موقع الويب يعني موقع الويب الخاص بالمجموعة وهو عبارة عن منصة يتم من خلالها تقديم خدماتنا ويمكن للمستخدمين الدخول إليها.", + "liveCareTermsConditions12": "الشروط والأحكام تعني شروط خدمات الرعاية الصحية عن بُعد هذه.", + "liveCareTermsConditions13": "المحتوى يعني أي رسالة أو بيانات أو معلومات أو نص أو صوت أو صور أو فيديو أو رسومات أو رمز أو أي مادة أخرى واردة في التطبيق أو موقع الويب أو يتم نقلها عبر التطبيق أو موقع الويب.", + "liveCareTermsHeading4": "2.\tتعهدات المستخدم", + "liveCareTermsConditions14": "2-1\tمن خلال دخولك إلى خدمات الرعاية الصحية عن بُعد واستخدامها، فإنك توافق على هذه الشروط والأحكام ويتعين عليك ألا تحاول الدخول إلى خدماتنا إذا كنت لا توافق عليها.", + "liveCareTermsConditions15": "2-2\tأنت تضمن وتقر وتتعهد بأن جميع المعلومات التي تقدمها عن نفسك وعن أي شخص آخر صحيحة ودقيقة.", + "liveCareTermsConditions16": "2-3\tخدمات الرعاية الصحية عن بعد مخصصة للاستخدام من قبل المرضى الموجودين فعلياً في المملكة العربية السعودية (المنطقة المسموح بها) ولا ينبغي استخدامها من قبل المرضى خارج المنطقة المسموح بها. بالموافقة على هذه الشروط والأحكام والدخول إلى خدمات الرعاية الصحية عن بُعد عبر التطبيق أو موقع الويب، فإنك تقر وتضمن لنا أنك موجود فعلياً في إحدى المناطق المسموح بها.", + "liveCareTermsHeading5": "3.\tالقيود على خدمات الرعاية الصحية عن بعد والمخاطر المرتبطة بها", + "liveCareTermsConditions17": "3-1\tلا تستخدم التطبيق أو موقع الويب أو خدمات الرعاية الصحية عن بُعد للتواصل بشأن الحالات الطبية الطارئة أو علاجها. لا التطبيق ولا موقع الويب ولا خدمات الرعاية الصحية عن بُعد توفر الرعاية في الحالات الطبية الطارئة. إذا كانت هناك حالة طبية طارئة عاجلة أو كانت هناك حاجة إلى استجابة سريعة فيما يتعلق بصحتك، فانتقل إلى أقرب مركز/مستشفى طوارئ أو اتصل على الفور بأي رقم هاتف للطوارئ.", + "liveCareTermsConditions18": "3-2\tخدمات الرعاية الصحية عن بعد ليست مناسبة للحالات الطبية الطارئة وقد تكون هناك بعض العلاجات أو الأدوية التي لا يمكن تقديمها دون استشارة متابعة في حضور المريض شخصياً. إذا كنت تعتقد أن لديك حالة طبية خطيرة، فعليك طلب استشارة شخصية مع أخصائي طبي مرخص.", + "liveCareTermsConditions19": "3-3\tتهدف خدمات الرعاية الصحية عن بُعد إلى توفير مزايا تشمل راحة المريض. لذلك، فهو قد يكون الأنسب كبديل للاستشارة الشخصية للأمور الطبية الروتينية. ومع ذلك، هناك مخاطر محتملة مرتبطة باستخدام أي خدمة للرعاية الصحية عن بُعد، بما في ذلك خدمات الرعاية الصحية عن بُعد، وقد تشمل هذه المخاطر على سبيل المثال لا الحصر ما يلي:", + "liveCareTermsConditions19a": "(1) المعلومات المنقولة قد لا تكون كافية (مثل ضعف دقة الصور) للسماح باتخاذ القرار الطبي المناسب من قبل الممارسين الصحيين", + "liveCareTermsConditions19b": "(2) قد يقرر الممارسين الصحيين أن المعلومات المنقولة ليست ذات جودة كافية، مما يستلزم مقابلة المريض وجهاً لوجه، أو على الأقل إعادة جدولة استشارة طبية صوتية ومرئية في وقت لاحق", + "liveCareTermsConditions19c": "(3) يمكن أن يحدث تأخير في تقديم الرعاية الطبية بسبب أوجه القصور أو الأعطال في الأجهزة الإلكترونية", + "liveCareTermsConditions19d": "(4) يعتمد الممارس الصحي على المريض لتقديم جميع المعلومات ذات الصلة شفوياً وعبر الاستخدام المناسب لجهاز الكاميرا الخاص بالمريض. من الممكن ألا يتم تقديم المعلومات إلى الممارس أو قد لا تكون واضحة بقدر الوضوح الذي يتوفر للممارس الصحي أثناء إجراء الفحص البدني الشخصي", + "liveCareTermsConditions19e": "(5) قد تفشل البروتوكولات الأمنية، مما يتسبب في انتهاك خصوصية المعلومات الطبية الشخصية. على الرغم من أننا نتخذ تدابير معقولة لحماية أمن المعلومات المنقولة عبر التطبيق و/أو الموقع الإلكتروني، إلا أنه لا يمكن أبدًا ضمان أن تكون عمليات الإرسال هذه آمنة تماماً، والمعلومات المنقولة عبر شبكات الاتصالات العامة خارجة عن سيطرتنا ولا يمكننا تأمينها.", + "liveCareTermsConditions20": "3-4\tفي حال كنت تستخدم خدمات الرعاية الصحية عن بُعد ولكنك تواجه أي حالة أو مخاوف مستمرة، فإننا نوصيك بحجز استشارة شخصية.", + "liveCareTermsHeading6": "4.\tإخلاء المسؤولية عن الضمان", + "liveCareTermsConditions21": "4-1\tنحن لا نضمن ولا نقر أو نتعهد بأن خدمات الرعاية الصحية عن بُعد ستلبي احتياجاتك، أو أنها ستكون مستمرة دون انقطاع أو سيتم توفيرها في الوقت المناسب أو أنها آمنة أو خالية من الأخطاء أو خالية من الفيروسات. لن نتحمل أي مسؤولية تجاهك في حالة تعذر حدوث جلسة خدمات الرعاية الصحية عن بُعد أو تمت مقاطعتها أو التخلي عنها بسبب عدم توفر منصتنا عبر الإنترنت ومع ذلك، في حالة تعذر علينا إجراء جلسة خدمات الرعاية الصحية عن بُعد بالكامل بسبب تعطل منصتنا، فلن نتقاضى منك أي أجور عن جلسة الاستشارة البديلة.", + "liveCareTermsConditions22": "4-2\tتُستثنى جميع الضمانات الصريحة أو الضمنية بموجب النظام، باستثناء تلك التي لا يمكن استثناؤها بشكل قانوني.", + "liveCareTermsConditions23": "4-3\tلا يوجد في هذا القسم 4 أي نص يُقصد منه استبعاد أو تقييد مسؤوليتنا عن أي مطالبة تتعلق بالإهمال الطبي، إلى الحد الذي لا يسمح لنا فيه النظام المعمول به في المنطقة المسموح بها ذات الصلة بالقيام بذلك.", + "liveCareTermsHeading7": "5.\tالأهلية والتسجيل والاستخدام", + "liveCareTermsConditions24": "5-1\tمن أجل الدخول إلى خدمات الرعاية الصحية عن بُعد، يتعين عليك أن تفي بمتطلبات الحد الأدنى للسن وهو 18 عاماً (يتم احتسابها وفقاً للتقويم الميلادي) وأن تتمتع بالأهلية القانونية الكاملة.", + "liveCareTermsConditions25": "5-2\tيجب أن يحصل أي شخص يقل عمره عن 18 عاماً (يتم احتسابها وفقاً للتقويم الميلادي) يقوم بالدخول إلى خدمات الرعاية الصحية عن بُعد على إذن من أحد الوالدين أو الوصي للقيام بذلك.", + "liveCareTermsConditions26": "5-3\tأنت تضمن وتقر وتتعهد بأن لديك الحق القانوني والقدرة على الدخول إلى التطبيق و/أو موقع الويب وفقاً لهذه الشروط والأحكام و، عند الاقتضاء، إجراء الحجوزات والمدفوعات من خلال التطبيق أو موقع الويب.", + "liveCareTermsConditions27": "5-4\tتتم عملية التسجيل وفق ما ورد في شروط استخدام الخدمات الإلكترونية", + "liveCareTermsConditions28": "5-5\tخدمات الرعاية الصحية عن بُعد مخصصة فقط لاستخدامك الشخصي واستخدام أطفالك تحت إشرافك. يتعين عليك ألا تشارك بيانات تسجيل الدخول الخاصة بك (اسم المستخدم وكلمة المرور وما إلى ذلك) مع أي شخص آخر ويجب أن تتخذ خطوات معقولة للحفاظ على أمان هذه البيانات. يتم تقديم خدماتنا على أساس أن لدينا معلومات معينة حول المستخدم، والتي قد تكون مرتبطة أو تم الحصول عليها من تفاصيل حسابك (العمر والجنس والتاريخ الطبي وما إلى ذلك). نحن لا نتحمل أي مسؤولية من أي نوع تجاهك أو تجاه أي شخص آخر إذا تم تقديم خدماتنا عبر حساب مستخدم إلى شخص آخر غير المستخدم المسجل. هذا مهم بشكل خاص لخدمات الرعاية الصحية عن بعد، حيث سيحتاج الممارس الصحي إلى تقييم المعلومات التي يقدمها المريض أثناء الاستشارة في سياق الملف الطبي للمريض وتاريخه.", + "liveCareTermsConditions29": "5-6\tأنت مسؤول عن التأكد من أن لديك جهازاً متصلاً بالإنترنت وقادراً على إرسال واستقبال الفيديو والصوت. نحن لسنا مسؤولين عن أي انقطاع في جلسة خدمات الرعاية الصحية عن بعد بسبب ناجم عن جهازك أو اتصالك بالإنترنت. أنت مسؤول عن جميع التكاليف المرتبطة باتصالك بالإنترنت واستخدامك لها.", + "liveCareTermsConditions30": "5-7\tقد نقوم من وقت لآخر بإصدار تحديثات لتطبيقنا و/أو موقع الويب الخاص بنا. يجب عليك التأكد من أنك تقوم بتشغيل أحدث إصدار من التطبيق. قد يؤدي استخدام الإصدارات القديمة من التطبيق إلى حدوث أخطاء فنية أثناء استخدامك لخدمات الرعاية الصحية عن بُعد وقد يعرضك ذلك لمخاطر أمنية إضافية.", + "liveCareTermsHeading8": "6.\tخدمات الرعاية الصحية عن بُعد", + "liveCareTermsConditions31": "خدمات الرعاية الصحية عن بُعد الخاصة بنا سوف تكون لتخصصات طبية محددة أو لمواعيد المتابعة أو إعادة صرف الدواء. ", + "liveCareTermsHeading9": "7.\tالأجور والسداد", + "liveCareTermsConditions32": "خدمات الرعاية الصحية عن بُعد الخاصة بنا سوف تكون لتخصصات طبية محددة أو لمواعيد المتابعة أو إعادة صرف الدواء. ", + "liveCareTermsConditions33": "7-1\tستتم الإشارة إلى أجور استشارة خدمات الرعاية الصحية عن بُعد قبل أن تختار المتابعة. بمجرد المتابعة، فإنك توافق على سداد الأجور. لا تشمل تلك الأجور الأولية أي أجور لاحقة مثل أجور الوصفات الطبية والاختبارات المعملية واستشارات المتابعة وما إلى ذلك من أجور وسيتم إبلاغك بها بعد الاستشارة الأولية.", + "liveCareTermsConditions34": "7-2\tيتم تحديد شروط وطرق السداد المعمول بها في شروط استخدام الخدمات عبر الإنترنت الخاصة بنا.", + "liveCareTermsConditions35": "7-3\tقد يكون من الممكن فرض أجور خدمات الرعاية الصحية عن بُعد مباشرة على مزود التأمين الطبي، على الرغم من أننا لا نقدم أي تعهد أو ضمان بأن هذا سيكون ممكناً في أي وقت. تضمنت شروط استخدام الخدمات عبر الإنترنت الخاصة بنا مزيد من التفاصيل حول ذلك. إذا كان هذا التسهيل الخدمي متاحاً واخترت استخدامه، فستحتاج إلى تزويدنا بتفاصيل شركة التأمين الطبي وتغطية وثيقة التأمين، ما لم تكن قد قمت بذلك مسبقاً وربطتها بحسابك. أنت تفوضنا بموجب هذا بالتواصل مع أي شركة تأمين وفق التفاصيل التي تقدمها لنا في وقت الشراء أو المرتبطة بحسابك من أجل الحصول على الموافقة على تكلفة الخدمات المقدمة لك. أنت تقر بأنه قد يُطلب منا تقديم تفاصيل محددة عن الخدمات الطبية التي تطلبها من أجل القيام بذلك وتوافق صراحة على هذا الإفصاح.", + "liveCareTermsHeading10": "8.\tالاتصالات و/أو الاستفسارات", + "liveCareTermsConditions36": "في حال وجود أي أسئلة و/أو استفسارات بخصوص استخدام خدمات الرعاية الصحية عن بُعد، يرجى التواصل معنا على: EServices.HMG@drsulaimanalhabib.com أو الاتصال على الرقم: 9553 525 011", + "liveCareTermsHeading11": "9.\tحدود المسؤولية", + "liveCareTermsConditions37": "9-1\tمع مراعاة أي مسؤوليات ينص عليها النظام والتي لا يمكن استبعادها بموجبه، لا تتحمل المجموعة أو أي من مديرينا أو موظفينا أو وكلائنا أو مقاولينا المسؤولية تجاهك عن أي خسائر وأضرار ومسؤوليات ومطالبات ونفقات (بما في ذلك على سبيل المثال لا الحصر الأتعاب القانونية وتكاليف الدفاع أو التسوية) أيا كانت، سواء كانت مباشرة أو غير مباشرة أو تبعية، ناشئة عن أو متعلقة باستخدامك لخدمات الرعاية الصحية عن بُعد، أياً كان سبب نشوئها، سواء كان ذلك بموجب عقد أو المسؤولية التقصيرية (بما في ذلك الإهمال) أو النظام أو غير ذلك.", + "liveCareTermsConditions38": "9-2\tلن نكون مسؤولين تجاهك أو تجاه أي شخص آخر عن أي خسارة ناتجة عن سبب خارج عن سيطرتنا. يتضمن ذلك تعطل المعدات الإلكترونية أو الميكانيكية أو خطوط الاتصالات (بما في ذلك الهاتف والكابل والإنترنت)، أو الدخول غير المصرح به، أو أفعال أو إغفالات الأطراف الثالثة، أو الفيروسات، أو السرقة، أو أخطاء المشغل، أو الطقس القاسي أو غير العادي (بما في ذلك الفيضانات، أو الزلازل، أو أي من أفعال القضاء والقدر الأخرى)، أو الحرائق والحروب والتمرد والأعمال الإرهابية وأعمال شغب والنزاع العمالي أو المشاكل العمالية الأخرى أو الحوادث أو الطوارئ أو التأخير أو الحجز الزائد أو الإلغاء، أو الزوال الملكي أو أي إجراء حكومي.", + "liveCareTermsHeading12": "10.\tإنهاء الاستخدام", + "liveCareTermsConditions39": "10-1\tيجوز لنا التوقف عن توفير خدمات الرعاية الصحية عن بُعد في أي وقت ولأي سبب. لن نتحمل أي مسؤولية عن أي توقف، بما في ذلك عندما تكون خلال الاستشارات أو دورات العلاج باستخدام خدمات الرعاية الصحية عن بُعد. سنساعدك للتمكن من حجز الاستشارات الشخصية بدلاً من ذلك أو في نقل المعلومات ذات الصلة إلى مزود طبي بديل لتمكينك من مواصلة العلاج (مع مراعاة أنظمة حماية البيانات والسرية والممارسات الطبية والممارسات الطبية الجيدة السائدة).", + "liveCareTermsConditions40": "10-2\tيجوز لنا إنهاء استخدامك ودخولك إلى خدمات الرعاية الصحية عن بُعد لأي سبب أو بدون سبب، في أي وقت، سواء بإشعارك أو بدونه ودون تحمل أي مسؤولية تجاهك، ومع ذلك قد تنشأ هذه المسؤولية (على الرغم من أننا سنأخذ واجباتنا الطبية تجاهك بعين الاعتبار في حالة القيام بذلك).", + "liveCareTermsConditions41": "10-3\tستظل جميع القيود وجميع إخلاءات الطرف من المسؤولية والاستثناءات والقيود الخاصة بمسؤوليتنا سارية بعد أي إنهاء. عند الإنهاء، يتعين عليك عدم الدخول إلى أو استخدام التطبيق و/أو موقع الويب بشكل مباشر أو غير مباشر.", + "liveCareTermsHeading13": "11.\tالقانون الحاكم والاختصاص القضائي", + "liveCareTermsConditions42": "النظام المعمول به على التطبيق و/أو شروط وأحكام مستخدم موقع الويب والنزاعات الناشئة عنها أو المتعلقة بها هي أنظمة المملكة العربية السعودية. وأنت تخضع بشكل لا رجوع فيه للاختصاص القضائي الحصري لمحاكم المملكة العربية السعودية.", + "liveCareTermsHeading14": "12.\tالإقرارات", + "liveCareTermsConditions43": "12-1\tباستثناء ما هو منصوص عليه صراحةً في هذه الشروط والأحكام أو ما يسمح به أي نظام معمول به، فإنك توافق على ما يلي:", + "liveCareTermsConditions43a": "(1) أنك قد قرأت وفهمت هذه الشروط والأحكام والبنود الأخرى التي تشكل جزءاً من اتفاقيتك معنا", + "liveCareTermsConditions43b": "(2) أنك قد قرأت سياسة الخصوصية الخاصة بنا وتوافق بموجبه صراحة على استخدامنا لمعلوماتك الشخصية كما هو موضح فيها. بالإضافة إلى ذلك، أنت تدرك أن رقم الاتصال أو عنوان البريد الإلكتروني الذي زودتنا به عند التسجيل سيتم استخدامه للاتصالات من قبل المجموعة من خلال الرسائل القصيرة أو البريد الإلكتروني أو أي طريقة أخرى فيما يتعلق بخدمات الرعاية الصحية عن بُعد، بما في ذلك إشعارات المواعيد والخدمات الطبية الحالية.", + "liveCareTermsConditions43c": "(3) أنت تدرك أن لديك الحق في حجب أو سحب موافقتك بشأن استخدام التطبيق و/أو موقع الويب في سياق رعايتك في أي وقت، دون التأثير على حقك في تلقي الرعاية أو العلاج في المستقبل؛", + "liveCareTermsConditions43d": "(4) أنت تدرك أنك قد تتوقع الحصول على مزايا من استخدام التطبيق و/أو موقع الويب فيما يتعلق برعايتك الصحية، ولكن لا يمكن ضمان أي نتائج أو تأكيدها؛", + "liveCareTermsConditions43e": "(5) أنت تدرك أنه قد تتم مشاركة معلومات الرعاية الصحية الخاصة بك مع موظفين آخرين في المجموعة لأغراض جدولة المواعيد والفواتير؛", + "liveCareTermsConditions43f": "(6) أنت تدرك أن صور الفيديو والتسجيلات الصوتية الخاصة بك قد يتم التقاطها وتخزينها إلكترونياً. وأنت تدرك كذلك أنه يمكن استخدام هذه التسجيلات لاحقاً فيما يتعلق بالإجراءات النظامية إذا لزم الأمر ولكن لن يتم استخدامها لأي غرض بخلاف توفير خدمات الرعاية الصحية عن بُعد وخدمات المتابعة الطبية لك. أنت تقر وتوافق على استخدام هذه الصور والتسجيلات الصوتية من قبلنا للأغراض الموضحة؛", + "liveCareTermsConditions43g": "(7) أنك تدرك مخاطر الاتصال عبر البريد الإلكتروني والرسائل النصية، ولا سيما مخاطر الخصوصية. أنت تدرك أن المجموعة لا يمكنها ضمان أمان وسرية البريد الإلكتروني أو الاتصالات النصية. لن تكون المجموعة مسؤولة عن الرسائل التي لم يتم استلامها أو تسليمها بسبب عطل فني أو الكشف عن معلومات سرية ما لم يكن ذلك بسبب سوء سلوك متعمد.", + "liveCareTermsHeading15": "13.\tشروط وأحكام عامة", + "liveCareTermsConditions44": "13-1\tيجوز لنا في أي وقت تغيير هذه الشروط والأحكام لأسباب أمنية أو نظامية أو تنظيمية ، أو لتعكس التحديثات أو التغييرات التي تطرأ على خدمات أو وظائف خدمات الرعاية الصحية عن بُعد أو التطبيق، عن طريق نشر الشروط والأحكام المتنوعة على التطبيق. سنخطرك في التطبيق بأن هذه الشروط والأحكام قد تغيرت قبل استخدامك التالي لخدمات الرعاية الصحية عن بُعد وستتاح لك الفرصة للموافقة على الشروط المعدلة قبل استخدام الخدمة مرة أخرى.", + "liveCareTermsConditions45": "13-2\tيجوز لنا أداء أي من التزاماتنا، وممارسة أي من الحقوق الممنوحة لنا بموجب هذه الشروط والأحكام، من خلال طرف ثالث. يجوز لنا التنازل عن أي من حقوقنا والتزاماتنا أو جميعها بموجب هذه الشروط والأحكام إلى أي طرف ثالث.", + "liveCareTermsConditions46": "13-3\tإذا تبين أن أي بند أو جزء من أي بند من هذه الشروط والأحكام باطل أو غير قابل للإنفاذ أو غير صالح، فسيتم فصله عن هذه الشروط والأحكام، تاركاً باقي الشروط سارية المفعول بكامل الأثر والنفاذ، بشرط ألا يغير فصل ذلك البند من الطبيعة الأساسية لهذه الشروط والأحكام.", + "liveCareTermsHeading16": "14.\tالشكاوى", + "liveCareTermsConditions47": "خدمات الرعاية الصحية عن بُعد الخاصة بنا سوف تكون لتخصصات طبية محددة أو لمواعيد المتابعة أو إعادة صرف الدواء. ", + "liveCareTermsConditions48": "إذا كانت لديك أي شكاوى أو مخاوف بشأن التطبيق و/أو موقع الويب أو خدماتنا أو كيفية تعاملنا مع معلوماتك الشخصية، فيرجى التواصل معنا على: EServices.HMG@drsulaimanalhabib.com أو الاتصال على الرقم: 9553 525 011" } diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 1f006d2c..aaafb124 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1959,7 +1959,88 @@ "hide": "Hide", "viewPatientQR": "View Patient QR Code", "patientQR": "Patient QR Code", - "forText": "For" + "forText": "For", + "liveCareTermsHeading": "TELEHEALTH SERVICES TERMS OF USE", + "liveCareTermsIntroHeading": "INTRODUCTION", + "liveCareTermsConditions1": "Dr. Sulaiman Al Habib Medical Services Group Company (the “Group”) is one of the leaders in the healthcare sector in the Kingdom of Saudi Arabia with an aim to always improve its services in the region, and as a step to take our services to another level and to facilitate the process of receiving healthcare services we have adopted the approach of providing some of our services remotely to patients. These terms apply to our Telehealth Services, which enable consultations to occur between a doctor and a patient over the internet, using audio or and video conferencing technology (often referred to as “Telehealth Services”).", + "liveCareTermsConditions2": "The purpose of the Telehealth Services is to facilitate communication between healthcare practitioners and patients to provide healthcare services through advanced electronic communication means, which allows healthcare professionals to evaluate, diagnose, treat and follow-up with patients remotely without the need for patients to attend in person.).", + "liveCareTermsHeading2": "AGREEMENT", + "liveCareTermsConditions3": "Telehealth Services is delivered over the Group’s mobile application or and Website. The following terms apply to the Telehealth Services: ", + "liveCareTermsConditions3a": " - Website Terms of Use", + "liveCareTermsConditions3b": " - App User Agreement", + "liveCareTermsConditions3c": " - Online Services Terms of Use", + "liveCareTermsConditions3d": " - These Telehealth Services User Terms of Use", + "liveCareTermsConditions4": "By agreeing to proceed with the Telehealth Services and accepting these terms, you enter into an agreement with The Group incorporating all the above terms (Telehealth Services Agreement).", + "liveCareTermsConditions5": "Our Privacy Policy should also be read. It contains details about how we use your personal information and your rights. By proceeding with the Telehealth Services, you agree to our use of your personal information in accordance with our Privacy Policy and you expressly consent to our use of your personal information for the performance of the Telehealth Services and the discharge of our professional duties as a medical care provider.", + "liveCareTermsConditions6": "Our Privacy Policy should also be read. It contains details about how we use your personal information and your rights. By proceeding with the Telehealth Services, you agree to our use of your personal information in accordance with our Privacy Policy and you expressly consent to our use of your personal information for the performance of the Telehealth Services and the discharge of our professional duties as a medical care provider.", + "liveCareTermsHeading3": "1. DEFINITIONS", + "liveCareTermsConditions7": "Group means Dr. Sulaiman Al Habib Medical Services Group Company and its subsidiaries. ", + "liveCareTermsConditions8": "Telehealth Services means the Group’s telehealth service, enabling remote consultation, diagnosis and follow-up between the patient and doctor, using the Application and or the Website.", + "liveCareTermsConditions9": "Our Services means certain services provided by the Group which covers viewing patients’ medical records and medical test results, providing medical diagnostics in addition to the availability of voice and video Telehealth Service between healthcare practitioners and patients.", + "liveCareTermsConditions10": "Application means Dr. ALHABIB Application which is a platform where Our Services are provided through and accessible by users. ", + "liveCareTermsConditions11": "Website means the Group internet website which is a platform where Our Services are provided through and accessible by users. ", + "liveCareTermsConditions12": "Terms & Conditions means these Telehealth Services Terms of Use. ", + "liveCareTermsConditions13": "Content means any message, data, information, text, sound, photos, video, graphics, code or any other material contained on the Application and or the Website or transmitted via the Application and or the Website. ", + "liveCareTermsHeading4": "2. USER UNDERTAKINGS", + "liveCareTermsConditions14": "2.1 By accessing and using Telehealth Services, you agree to these Terms & Conditions, and you should not attempt to access Our Services if you do not agree. ", + "liveCareTermsConditions15": "2.2 You warrant, represent and undertake that all information you provide about yourself and about anyone else is true and accurate. ", + "liveCareTermsConditions16": "2.3 Telehealth Services is intended for use by patients physically located in the Kingdom Saudi Arabia (Permitted Territory) and should not be used by patients outside the Permitted Territory. By agreeing to these Terms & Conditions and accessing Telehealth Services via the Application and or the Website, you represent and warrant to us that you are physically located in one of the Permitted Territory. ", + "liveCareTermsHeading5": "3. LIMITATIONS OF TELEHEALTH SERVICES AND ASSOCIATED RISKS", + "liveCareTermsConditions17": "3.1 Do not use the Application and or the Website or the Telehealth Services to communicate or treat medical emergencies. Neither the Application nor the Website nor Telehealth Services are intended for the provision of care in an emergency. IF THERE IS AN URGENT MEDICAL EMERGENCY OR A RESPONSE IS NEEDED QUICKLY REGARDING YOUR HEALTH, GO TO A NEARBY EMERGENCY CENTER/HOSPITAL OR CALL IMMEDIATELY ANY EMERGENCY PHONE NUMBER. ", + "liveCareTermsConditions18": "3.2 Telehealth Services is not appropriate for emergency conditions and there may be some treatments or medicines which cannot be provided without follow-up physical consultation. If you believe you have a serious medical condition, you should seek an in-person consultation with a licensed medical professional.", + "liveCareTermsConditions19": "3.3 Telehealth Services is intended to provide benefits including patient convenience. As such, it is most suitable as an alternative to in-person consultation for routine medical matters. However, there are potential risks associated with the use of any telehealth service, including Telehealth Services, and these risks may include but not be limited to the following: ", + "liveCareTermsConditions19a": "(a) information transmitted may not be sufficient (e.g. poor resolution of images) to allow for appropriate medical decision making by healthcare practitioners.", + "liveCareTermsConditions19b": "(b) healthcare practitioners may determine that the transmitted information is of inadequate quality, thus necessitating a face-to-face meeting with the patient, or at least a rescheduled voice and video medical consult. ", + "liveCareTermsConditions19c": "(c) delays in medical care could occur due to deficiencies or failures of the electronic devices. ", + "liveCareTermsConditions19d": "(d) the practitioner is dependent on the patient to provide all relevant information orally and via the appropriate use of the patient’s own camera device. It is possible that information may not be provided to the practitioner or may not be apparent which would have been apparent to the practitioner during an in-person physical examination. ", + "liveCareTermsConditions19e": "(e) security protocols could fail, causing a breach of privacy of personal medical information. Whilst we take reasonable measures to protect the security of the information transmitted over the Application and or the Website, such transmissions can never be guaranteed to be completely secure and information transmitted over public telecommunications networks is outside our control and cannot be secured by us.", + "liveCareTermsConditions20": "3.4 If you use Telehealth Services but are experiencing any ongoing condition or concerns, we recommend you schedule an in-person consultation. ", + "liveCareTermsHeading6": "4. DISCLAIMER OF WARRANTY ", + "liveCareTermsConditions21": "4.1 We do not warrant, represent or undertake that Telehealth Services will meet your needs, or that it will be uninterrupted, timely, secure, error-free or free from viruses. We shall have no liability to you if a Telehealth Services session cannot occur or is interrupted or abandoned due to any unavailability of our online platform, however, where the Telehealth Services session cannot be delivered fully due to the downtime of our own platform, we will not charge you for a replacement consultation.", + "liveCareTermsConditions22": "4.2 All warranties expressed or implied by law, except those that cannot be lawfully excluded, are excluded. ", + "liveCareTermsConditions23": "4.3 Nothing in this section 4 is intended to exclude or limit our liability for any medical negligence claim, to the extent the applicable law in the relevant Permitted Territory does not permit us to do so.", + "liveCareTermsHeading7": "5. ELIGIBILITY, REGISTRATION AND USE", + "liveCareTermsConditions24": "5.1 To access Telehealth Services, you must satisfy the minimum age requirement of 18 years (measured according to the Gregorian calendar) and have full legal capacity. ", + "liveCareTermsConditions25": "5.2 Any person under the age of 18 years (measured according to the Gregorian calendar) accessing Telehealth Services must have permission from their parent or guardian to do so.", + "liveCareTermsConditions26": "5.3 You warrant, represent and undertake that you possess the legal right and ability to access the Application and or the Website in accordance with these Terms & Conditions and, where relevant, to make bookings and payments through the Application and or the Website.", + "liveCareTermsConditions27": "5.4 The registration process is as provided in the Online Services Terms of Use.", + "liveCareTermsConditions28": "5.5 Telehealth Services is intended only for your own personal use and the use of your children under your supervision. You must not share your user access details (log-in name, password etc.) with any other person and must take reasonable steps to maintain the security of such details. Our services are delivered on the basis that we have certain information about the user, which may be connected to or obtained from your account details (age, gender, medical history etc.). We accept no responsibility whatsoever to you or any other person if our services are delivered via a user account to a person other than the registered user. This is particularly important for telehealth services, where the practitioner will need to assess the information, the patient provides during the consultation in the context of the patient’s medical file and history.", + "liveCareTermsConditions29": "5.6 You are responsible for ensuring you have a device connected to the internet and capable of transmitting and receiving video and sound. We are not responsible for any failure of a Telehealth Services session caused by your device or your internet connection. You are responsible for all costs associated with your own internet connection and use.", + "liveCareTermsConditions30": "5.7 From time to time we may release updates to our Application and or the Website. You should ensure that you are running the latest version of the Application. Use of old versions of the Application may lead to technical errors in your use of the Telehealth Services and may expose you to additional security risks.", + "liveCareTermsHeading8": "6. TELEHEALTH SERVICES", + "liveCareTermsConditions31": "Our Telehealth Services will be for specific medical specialties or follow-up or medication refill appointments.", + "liveCareTermsHeading9": "7. CHARGES AND PAYMENT", + "liveCareTermsConditions32": "Our Telehealth Services will be for specific medical specialties or follow-up or medication refill appointments.", + "liveCareTermsConditions33": "7.1 The charges for a Telehealth Services consultation will be indicated before you choose to proceed. By proceeding, you agree to pay the charges. Any subsequent charges, such as prescription charges, lab tests, follow-up consultations etc. are excluded from the initial charge and will be communicated to you following the initial consultation. ", + "liveCareTermsConditions34": "7.2 The applicable payment terms and methods are set out in our Online Services Terms of Use. ", + "liveCareTermsConditions35": "7.3 It may be possible to charge Telehealth Services directly to a provider of medical insurance, although we give no undertaking or warranty that this shall be possible at any given time. Further details are set out in our Online Services Terms of Use. If such facility is made available and you choose to use it, you will need to provide us with details of your medical insurer and policy coverage, unless you have already done so and linked them to your account. You hereby authorise us to contact any such insurer with the details you provide at the time of purchase, or which are linked to your account to seek approval of the cost of your services. You acknowledge that we may be required to provide specific details of the medical services sought by you to do so and you expressly consent to such disclosure.", + "liveCareTermsHeading10": "8. COMMUNICATION AND/OR INQUIRIES", + "liveCareTermsConditions36": "In case there are any questions and/or inquiries regarding the use of Telehealth Services please contact: EServices.HMG@drsulaimanalhabib.com or call 011 525 9553", + "liveCareTermsHeading11": "9. LIMITATION OF LIABILITY ", + "liveCareTermsConditions37": "9.1 Subject to any responsibilities implied by law and which cannot be excluded, neither the Group nor any of our directors, employees, agents or contractors, are liable to you for any losses, damages, liabilities, claims and expenses (including without limitation but not limited to legal costs and defense or settlement costs) whatsoever, whether direct, indirect or consequential, arising out of or referable to your use of Telehealth Services , however caused, whether in contract, tort (including negligence), statute or otherwise. ", + "liveCareTermsConditions38": "9.2 We will not be liable to you or anyone else for any loss resulting from a cause over which we do not have control. This includes failure of electronic or mechanical equipment or communications lines (including telephone, cable and Internet), unauthorized access, acts or omissions of third parties, viruses, theft, operator errors, severe or extraordinary weather (including flood, earthquake, or other act of God), fire, war, insurrection, terrorist act, riot, labor dispute or other labor problems, accident, emergency, delay, overbooking, cancellation, Royal demise or action of government", + "liveCareTermsHeading12": "10. TERMINATION OF USE", + "liveCareTermsConditions39": "10.1 We may discontinue the provision of Telehealth Services at any time and for any reason. We shall have no liability for any discontinuation, including where you are part-way through a series of consultations or courses of treatment using Telehealth Services. We will assist in enabling you to book in-person consultations instead or in transferring relevant information to an alternative medical provider to enable you to continue treatment (subject to data protection, confidentiality and medical practice laws and to prevailing medical good practice).", + "liveCareTermsConditions40": "10.2 We may terminate your use and access to Telehealth Services for any or no reason, at any time, with or without notice to you and without incurring any liability to you, however that liability may arise (although we will consider our medical duties to you, if doing so). ", + "liveCareTermsConditions41": "10.3 All restrictions and all disclaimers and exclusions and limitations of our liability will survive any termination. Upon termination, you must not directly or indirectly access or use the Application and or the Website.", + "liveCareTermsHeading13": "11. GOVERNING LAW AND JURISDICTION ", + "liveCareTermsConditions42": "The law applicable to the Application and or the Website User Terms and Conditions and to disputes arising out of or in connection with them are the laws of The Kingdom of Saudi Arabia. You irrevocably submit to the exclusive jurisdiction of the courts of the Kingdom of Saudi Arabia. ", + "liveCareTermsHeading14": "12. ACKNOWLEDGEMENTS", + "liveCareTermsConditions43": "12.1 Except as expressly set out in these Terms & Conditions or as permitted by any applicable law, you agree on the following: ", + "liveCareTermsConditions43a": "(a) you have read and understand these Terms & Conditions and the other terms forming part of your Agreement with us; ", + "liveCareTermsConditions43b": "(b) you have read our Privacy Policy and hereby expressly consent to our use of your personal information as described. In addition, you understand that the contact number or email address that you have provided on registration will be used for communications by the Group through SMS, email or any other method in connection with the Telehealth Services, including for appointment notifications and current medical services;", + "liveCareTermsConditions43c": "(c) you understand that you have the right to withhold or withdraw your consent to the use of the Application and or the Website in the course of your care at any time, without affecting your right to future care or treatment; ", + "liveCareTermsConditions43d": "(d) you understand that you may expect to receive benefits from the use of Application and or the Website concerning your healthcare, but that no results can be guaranteed or assured; ", + "liveCareTermsConditions43e": "(e) you understand that your healthcare information may be shared with other employees in the Group for scheduling and billing purposes;", + "liveCareTermsConditions43f": "(f) you understand that video images and audio recordings of you may be captured and stored electronically. You understand that these recordings may be later used in connection with legal proceedings if necessary but will not otherwise be used for any purpose other than providing Telehealth Services and follow-up medical services to you. You acknowledge and consent to the use of these images and audio recordings by us for the purposes described; ", + "liveCareTermsConditions43g": "(g) you understand the risks of communicating by email and text messages, particularly the privacy risks. You understand that the Group cannot guarantee the security and confidentiality of email or text communication. The Group will not be responsible for messages that are not received or delivered due to technical failure, or for disclosure of confidential information unless caused by intentional misconduct.", + "liveCareTermsHeading15": "13. GENERAL TERMS AND CONDITIONS ", + "liveCareTermsConditions44": "13.1 We may at any time vary these Terms & Conditions for security, legal or regulatory reasons, or to reflect updates or changes to the services or functionality of Telehealth Services or the Application, by publishing the varied Terms & Conditions on the Application. We will notify you on the Application that these Terms & Conditions have changed before your next use of the Telehealth Services and you will have the opportunity to agree to the revised terms before using the service again. ", + "liveCareTermsConditions45": "13.2 We may perform any of our obligations, and exercise any of the rights granted to us under these Terms & Conditions, through a third party. We may assign any or all our rights and obligations under these Terms & Conditions to any third party.", + "liveCareTermsConditions46": "13.3 If any clause or part of any clause of these Terms & Conditions is found to be void, unenforceable or invalid, then it will be severed from these Terms & Conditions, leaving the remainder in full force and effect, provided that the severance has not altered the basic nature of these Terms & Conditions. ", + "liveCareTermsHeading16": "14. COMPLAINTS", + "liveCareTermsConditions47": "Our Telehealth Services will be for specific medical specialties or follow-up or medication refill appointments.", + "liveCareTermsConditions48": "If you have any complaints or concerns about the Application and or the Website, Our Services, or how we handle your personal information please contact us on: EServices.HMG@drsulaimanalhabib.com or call 011 525 9553" } diff --git a/lib/features/immediate_livecare/immediate_livecare_view_model.dart b/lib/features/immediate_livecare/immediate_livecare_view_model.dart index 84a79c02..e2e8d2cb 100644 --- a/lib/features/immediate_livecare/immediate_livecare_view_model.dart +++ b/lib/features/immediate_livecare/immediate_livecare_view_model.dart @@ -59,16 +59,25 @@ class ImmediateLiveCareViewModel extends ChangeNotifier { bool isShowNotEligibleDialog = false; String notEligibleErrorMsg = ""; + bool isTermsAccepted = false; + initImmediateLiveCare() { _appState = getIt(); immediateLiveCareClinicsList = []; mainLiveCareClinicList = []; isImmediateLiveCareClinicsLoading = true; + isTermsAccepted = false; liveCareSelectedCallType = 0; // 1- Video, 2- Audio, 3- Phone immediateLiveCareSelectedClinic = GetLiveCareClinicListResponseModel(); liveCareImmediateAppointmentFeesList = LiveCareImmediateAppointmentFeesList(); } + + onTermsAccepted() { + isTermsAccepted = !isTermsAccepted; + notifyListeners(); + } + setLiveCareSelectedCallType(int value) { liveCareSelectedCallType = value; notifyListeners(); diff --git a/lib/features/payfort/payfort_view_model.dart b/lib/features/payfort/payfort_view_model.dart index 031c3182..c4f96857 100644 --- a/lib/features/payfort/payfort_view_model.dart +++ b/lib/features/payfort/payfort_view_model.dart @@ -418,7 +418,7 @@ class PayfortViewModel extends ChangeNotifier { payfortRequestInsertModel.sHAResponsePhase = payfortProjectDetailsRespModel!.shaResponse; payfortRequestInsertModel.signature = sdkTokenResponse!.signature; payfortRequestInsertModel.language = getIt.get().isArabic() ? "ar" : "en"; - payfortRequestInsertModel.integrationId = "1"; + payfortRequestInsertModel.integrationId = "1"; // Payfort integration ID payfortRequestInsertModel.isSchedule = isScheduleLiveCare; payfortRequestInsertModel.appointmentDate = appointmentDate; payfortRequestInsertModel.appointmentNo = appointmentNo; diff --git a/lib/features/paytabs/paytabs_view_model.dart b/lib/features/paytabs/paytabs_view_model.dart index 508753ef..b2563452 100644 --- a/lib/features/paytabs/paytabs_view_model.dart +++ b/lib/features/paytabs/paytabs_view_model.dart @@ -12,10 +12,12 @@ import 'package:flutter_paytabs_bridge/flutter_paytabs_bridge.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/tamara_request_model.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/paytabs_config.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/payfort_request_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/payfort_response_insert_model.dart'; @@ -365,7 +367,53 @@ class PayTabsViewModel extends ChangeNotifier { } } - startAlternatePayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) { + startAlternatePayment( + {required String liveServiceID, + required String appointmentDate, + required String appointmentNo, + required String doctorID, + required String transactionID, + required String amount, + required String fileNumber, + required String serviceID, + required String projectID, + required bool isSchedule, + Function(PaytabsTransactionResponseModel)? onSuccess, + Function(String)? onError}) async { + LoaderBottomSheet.showLoader(); + + TamaraRequestModel tamaraRequestModel = TamaraRequestModel(); + + tamaraRequestModel.merchantReference = transactionID; + tamaraRequestModel.merchantIdentifier = "Tamara"; + tamaraRequestModel.clientRequestID = transactionID; + tamaraRequestModel.amount = num.parse(amount); + tamaraRequestModel.currency = "SR"; + tamaraRequestModel.language = appState.isArabic() ? 'AR' : 'EN'; + tamaraRequestModel.commandType = "PURCHASE"; + tamaraRequestModel.customerEmail = "CustID_$fileNumber@HMG.com"; + tamaraRequestModel.orderDescription = "Ancillary Order Payment"; + tamaraRequestModel.isInstallment = true; + tamaraRequestModel.projectID = num.parse(projectID); + tamaraRequestModel.accessCode = getIt.get().getAuthenticatedUser()!.mobileNumber!; + tamaraRequestModel.appointmentNo = appointmentNo; + tamaraRequestModel.customerName = "${getIt.get().getAuthenticatedUser()!.firstName} ${getIt.get().getAuthenticatedUser()!.lastName}"; + ; + tamaraRequestModel.fileNumber = fileNumber; + tamaraRequestModel.patientOutSA = getIt.get().getAuthenticatedUser()!.outSa == 1 ? true : false; + tamaraRequestModel.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken); + tamaraRequestModel.latitude = appState.userLat.toString(); + tamaraRequestModel.longitude = appState.userLong.toString(); + tamaraRequestModel.serviceID = serviceID; + tamaraRequestModel.liveServiceID = liveServiceID; + tamaraRequestModel.doctorID = (doctorID != "") ? doctorID.toString() : ""; + tamaraRequestModel.appointmentDate = appointmentDate; + tamaraRequestModel.isSchedule = isSchedule; + + await getIt.get().tamaraRequestInsert(tamaraRequestModel: tamaraRequestModel); + + LoaderBottomSheet.hideLoader(); + bool _callbackFired = false; // prevent duplicate callback invocations FlutterPaytabsBridge.startAlternativePaymentMethod(paymentConfiguration, (event) { diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 536f3355..7dbcfd75 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1951,5 +1951,86 @@ abstract class LocaleKeys { static const viewPatientQR = 'viewPatientQR'; static const patientQR = 'patientQR'; static const forText = 'forText'; + static const liveCareTermsHeading = 'liveCareTermsHeading'; + static const liveCareTermsIntroHeading = 'liveCareTermsIntroHeading'; + static const liveCareTermsConditions1 = 'liveCareTermsConditions1'; + static const liveCareTermsConditions2 = 'liveCareTermsConditions2'; + static const liveCareTermsHeading2 = 'liveCareTermsHeading2'; + static const liveCareTermsConditions3 = 'liveCareTermsConditions3'; + static const liveCareTermsConditions3a = 'liveCareTermsConditions3a'; + static const liveCareTermsConditions3b = 'liveCareTermsConditions3b'; + static const liveCareTermsConditions3c = 'liveCareTermsConditions3c'; + static const liveCareTermsConditions3d = 'liveCareTermsConditions3d'; + static const liveCareTermsConditions4 = 'liveCareTermsConditions4'; + static const liveCareTermsConditions5 = 'liveCareTermsConditions5'; + static const liveCareTermsConditions6 = 'liveCareTermsConditions6'; + static const liveCareTermsHeading3 = 'liveCareTermsHeading3'; + static const liveCareTermsConditions7 = 'liveCareTermsConditions7'; + static const liveCareTermsConditions8 = 'liveCareTermsConditions8'; + static const liveCareTermsConditions9 = 'liveCareTermsConditions9'; + static const liveCareTermsConditions10 = 'liveCareTermsConditions10'; + static const liveCareTermsConditions11 = 'liveCareTermsConditions11'; + static const liveCareTermsConditions12 = 'liveCareTermsConditions12'; + static const liveCareTermsConditions13 = 'liveCareTermsConditions13'; + static const liveCareTermsHeading4 = 'liveCareTermsHeading4'; + static const liveCareTermsConditions14 = 'liveCareTermsConditions14'; + static const liveCareTermsConditions15 = 'liveCareTermsConditions15'; + static const liveCareTermsConditions16 = 'liveCareTermsConditions16'; + static const liveCareTermsHeading5 = 'liveCareTermsHeading5'; + static const liveCareTermsConditions17 = 'liveCareTermsConditions17'; + static const liveCareTermsConditions18 = 'liveCareTermsConditions18'; + static const liveCareTermsConditions19 = 'liveCareTermsConditions19'; + static const liveCareTermsConditions19a = 'liveCareTermsConditions19a'; + static const liveCareTermsConditions19b = 'liveCareTermsConditions19b'; + static const liveCareTermsConditions19c = 'liveCareTermsConditions19c'; + static const liveCareTermsConditions19d = 'liveCareTermsConditions19d'; + static const liveCareTermsConditions19e = 'liveCareTermsConditions19e'; + static const liveCareTermsConditions20 = 'liveCareTermsConditions20'; + static const liveCareTermsHeading6 = 'liveCareTermsHeading6'; + static const liveCareTermsConditions21 = 'liveCareTermsConditions21'; + static const liveCareTermsConditions22 = 'liveCareTermsConditions22'; + static const liveCareTermsConditions23 = 'liveCareTermsConditions23'; + static const liveCareTermsHeading7 = 'liveCareTermsHeading7'; + static const liveCareTermsConditions24 = 'liveCareTermsConditions24'; + static const liveCareTermsConditions25 = 'liveCareTermsConditions25'; + static const liveCareTermsConditions26 = 'liveCareTermsConditions26'; + static const liveCareTermsConditions27 = 'liveCareTermsConditions27'; + static const liveCareTermsConditions28 = 'liveCareTermsConditions28'; + static const liveCareTermsConditions29 = 'liveCareTermsConditions29'; + static const liveCareTermsConditions30 = 'liveCareTermsConditions30'; + static const liveCareTermsHeading8 = 'liveCareTermsHeading8'; + static const liveCareTermsConditions31 = 'liveCareTermsConditions31'; + static const liveCareTermsHeading9 = 'liveCareTermsHeading9'; + static const liveCareTermsConditions32 = 'liveCareTermsConditions32'; + static const liveCareTermsConditions33 = 'liveCareTermsConditions33'; + static const liveCareTermsConditions34 = 'liveCareTermsConditions34'; + static const liveCareTermsConditions35 = 'liveCareTermsConditions35'; + static const liveCareTermsHeading10 = 'liveCareTermsHeading10'; + static const liveCareTermsConditions36 = 'liveCareTermsConditions36'; + static const liveCareTermsHeading11 = 'liveCareTermsHeading11'; + static const liveCareTermsConditions37 = 'liveCareTermsConditions37'; + static const liveCareTermsConditions38 = 'liveCareTermsConditions38'; + static const liveCareTermsHeading12 = 'liveCareTermsHeading12'; + static const liveCareTermsConditions39 = 'liveCareTermsConditions39'; + static const liveCareTermsConditions40 = 'liveCareTermsConditions40'; + static const liveCareTermsConditions41 = 'liveCareTermsConditions41'; + static const liveCareTermsHeading13 = 'liveCareTermsHeading13'; + static const liveCareTermsConditions42 = 'liveCareTermsConditions42'; + static const liveCareTermsHeading14 = 'liveCareTermsHeading14'; + static const liveCareTermsConditions43 = 'liveCareTermsConditions43'; + static const liveCareTermsConditions43a = 'liveCareTermsConditions43a'; + static const liveCareTermsConditions43b = 'liveCareTermsConditions43b'; + static const liveCareTermsConditions43c = 'liveCareTermsConditions43c'; + static const liveCareTermsConditions43d = 'liveCareTermsConditions43d'; + static const liveCareTermsConditions43e = 'liveCareTermsConditions43e'; + static const liveCareTermsConditions43f = 'liveCareTermsConditions43f'; + static const liveCareTermsConditions43g = 'liveCareTermsConditions43g'; + static const liveCareTermsHeading15 = 'liveCareTermsHeading15'; + static const liveCareTermsConditions44 = 'liveCareTermsConditions44'; + static const liveCareTermsConditions45 = 'liveCareTermsConditions45'; + static const liveCareTermsConditions46 = 'liveCareTermsConditions46'; + static const liveCareTermsHeading16 = 'liveCareTermsHeading16'; + static const liveCareTermsConditions47 = 'liveCareTermsConditions47'; + static const liveCareTermsConditions48 = 'liveCareTermsConditions48'; } diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 57b2c71d..5c8bacf3 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -632,15 +632,33 @@ class _AppointmentPaymentPageState extends State { ).paddingSymmetrical(16.h, 16.h), ).paddingSymmetrical(24.h, 0.h).onPress(() { selectedPaymentMethod = "TAMARA"; + + transID = Utils.getAppointmentTransID( + widget.patientAppointmentHistoryResponseModel.projectID, + widget.patientAppointmentHistoryResponseModel.clinicID, + widget.patientAppointmentHistoryResponseModel.appointmentNo, + ); + if (appState.isPaytabsEnabled) { paytabsViewModel.setAlternatePaymentConfiguration( "Appointment Payment", num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString())); - paytabsViewModel.startAlternatePayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { - tamaraOrderID = transactionData.cartID!; - transID = transactionData.transactionReference!; - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); - await payfortViewModel.updateTamaraRequestStatus(responseMessage: "success", status: "14", clientRequestID: transID, tamaraOrderID: tamaraOrderID); - await payfortViewModel.markAppointmentAsTamaraPaid( + paytabsViewModel.startAlternatePayment( + liveServiceID: "0", + appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate!, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + doctorID: widget.patientAppointmentHistoryResponseModel.doctorID.toString(), + transactionID: transID, + amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(), + fileNumber: widget.patientAppointmentHistoryResponseModel.patientID, + serviceID: "2", + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + isSchedule: widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false, + onSuccess: (PaytabsTransactionResponseModel transactionData) async { + tamaraOrderID = transactionData.cartID!; + // transID = transactionData.transactionReference!; + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); + await payfortViewModel.updateTamaraRequestStatus(responseMessage: "success", status: "14", clientRequestID: transID, tamaraOrderID: tamaraOrderID); + await payfortViewModel.markAppointmentAsTamaraPaid( projectID: widget.patientAppointmentHistoryResponseModel.projectID, appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo); await myAppointmentsViewModel.createAdvancePayment( paymentMethodName: selectedPaymentMethod, diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart index c8fcdb36..53d4b1b1 100644 --- a/lib/presentation/book_appointment/book_appointment_page.dart +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -611,6 +611,7 @@ class _BookAppointmentPageState extends State { ), ); } else { + immediateLiveCareViewModel.initImmediateLiveCare(); Navigator.of(context).push( CustomPageRoute( page: SelectImmediateLiveCareClinicPage(), 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 57fd5e94..c15ebed4 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_details.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; @@ -15,6 +16,7 @@ import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_liveca import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/livecare_terms_conditions.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/widgets/select_livecare_call_type.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; @@ -286,7 +288,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "ريال").toLowerCase() == "ريال")), ], ).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 17.h), + SizedBox(height: 16.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -299,18 +301,59 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.currency ?? "ريال").toLowerCase() == "ريال")), ], ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), + GestureDetector( + onTap: immediateLiveCareVM.onTermsAccepted, + child: Row( + children: [ + Selector( + selector: (_, viewModel) => viewModel.isTermsAccepted, + shouldRebuild: (previous, next) => previous != next, + builder: (context, isTermsAccepted, child) { + return AnimatedContainer( + duration: const Duration(milliseconds: 200), + height: 24.h, + width: 24.h, + decoration: BoxDecoration( + color: isTermsAccepted ? AppColors.primaryRedColor : Colors.transparent, + borderRadius: BorderRadius.circular(6), + border: Border.all(color: isTermsAccepted ? AppColors.primaryRedBorderColor : AppColors.greyColor, width: 2.h), + ), + child: isTermsAccepted ? Icon(Icons.check, size: 16.f, color: Colors.white) : null, + ); + }, + ), + SizedBox(width: 12.h), + Row( + children: [ + "${LocaleKeys.iAcceptThe.tr(context: context)} ".toText14(isBold: true), + GestureDetector( + onTap: () { + Navigator.push( + context, + CustomPageRoute( + page: LiveCareTermsConditions(), + ), + ); + }, + child: LocaleKeys.termsConditoins.tr(context: context).toText14(isBold: true, isUnderLine: true, color: AppColors.primaryRedColor)), + ], + ), + ], + ), + ).paddingSymmetrical(24.h, 0.h), (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0" || immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0.0") // (true) ? CustomButton( + isDisabled: !immediateLiveCareVM.isTermsAccepted, text: LocaleKeys.confirmLiveCare.tr(context: context), onPressed: () async { await askVideoCallPermission(context).then((val) async { if (val) { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.confirmingLiveCareRequest.tr(context: context)); - await immediateLiveCareVM.addNewCallRequestForImmediateLiveCare( - "${appState.getAuthenticatedUser()!.patientId}${DateTime.now().millisecondsSinceEpoch}"); + await immediateLiveCareVM.addNewCallRequestForImmediateLiveCare("${appState.getAuthenticatedUser()!.patientId}${DateTime.now().millisecondsSinceEpoch}"); await immediateLiveCareVM.getPatientLiveCareHistory(); LoaderBottomSheet.hideLoader(); if (immediateLiveCareVM.patientHasPendingLiveCareRequest) { @@ -367,6 +410,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget { iconSize: 18.h, ).paddingSymmetrical(24.h, 24.h) : CustomButton( + isDisabled: !immediateLiveCareVM.isTermsAccepted, text: LocaleKeys.payNow.tr(context: context), onPressed: () async { await askVideoCallPermission(context).then((val) { diff --git a/lib/presentation/book_appointment/livecare/livecare_terms_conditions.dart b/lib/presentation/book_appointment/livecare/livecare_terms_conditions.dart new file mode 100644 index 00000000..522eb1bb --- /dev/null +++ b/lib/presentation/book_appointment/livecare/livecare_terms_conditions.dart @@ -0,0 +1,230 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; + +import '../../../generated/locale_keys.g.dart'; + +class LiveCareTermsConditions extends StatelessWidget { + const LiveCareTermsConditions({super.key}); + + @override + Widget build(BuildContext context) { + final isArabic = getIt.get().isArabic(); + + return Scaffold( + backgroundColor: AppColors.scaffoldBgColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: LocaleKeys.livecare.tr(context: context), + child: SingleChildScrollView( + padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeading(context, LocaleKeys.liveCareTermsHeading.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildHeading(context, LocaleKeys.liveCareTermsIntroHeading.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions1.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions2.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildHeading(context, LocaleKeys.liveCareTermsHeading2.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions3.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions3a.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions3b.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions3c.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions3d.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions4.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions5.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions6.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildHeading(context, LocaleKeys.liveCareTermsHeading3.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions7.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions8.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions9.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions10.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions11.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions12.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions13.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildHeading(context, LocaleKeys.liveCareTermsHeading4.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions14.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions15.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions16.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildHeading(context, LocaleKeys.liveCareTermsHeading5.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions17.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions18.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions19.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions19a.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions19b.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions19c.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions19d.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions19e.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions20.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildHeading(context, LocaleKeys.liveCareTermsHeading6.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions21.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions22.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions23.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildHeading(context, LocaleKeys.liveCareTermsHeading7.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions24.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions25.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions26.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions27.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions28.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions29.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions30.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildHeading(context, LocaleKeys.liveCareTermsHeading8.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions31.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildHeading(context, LocaleKeys.liveCareTermsHeading9.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions32.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions33.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions34.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions35.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildHeading(context, LocaleKeys.liveCareTermsHeading10.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions36.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildHeading(context, LocaleKeys.liveCareTermsHeading11.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions37.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions38.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildHeading(context, LocaleKeys.liveCareTermsHeading12.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions39.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions40.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions41.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildHeading(context, LocaleKeys.liveCareTermsHeading13.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions42.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildHeading(context, LocaleKeys.liveCareTermsHeading14.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions43.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions43a.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions43b.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions43c.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions43d.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions43e.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions43f.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions43g.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildHeading(context, LocaleKeys.liveCareTermsHeading15.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions44.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions45.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions46.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildHeading(context, LocaleKeys.liveCareTermsHeading16.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions47.tr(context: context), isArabic), + SizedBox(height: 12.h), + _buildContent(context, LocaleKeys.liveCareTermsConditions48.tr(context: context), isArabic), + SizedBox(height: 24.h), + ], + ), + ), + ), + ) + ], + ), + ); + } + + /// Helper method to build heading text with consistent styling + Widget _buildHeading(BuildContext context, String text, bool isArabic) { + return Text( + text, + style: TextStyle( + fontSize: 20.f, + fontFamily: isArabic ? 'CairoArabic' : 'Poppins', + fontWeight: FontWeight.w700, + color: AppColors.textColor, + letterSpacing: -0.5, + height: 1.4, + ), + ); + } + + /// Helper method to build content text with consistent styling + Widget _buildContent(BuildContext context, String text, bool isArabic) { + return Text( + text, + style: TextStyle( + fontSize: 16.f, + fontFamily: isArabic ? 'CairoArabic' : 'Poppins', + color: AppColors.textColor, + letterSpacing: -0.3, + height: 1.5, + ), + ); + } +} diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index db300d98..bf1a7b62 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -1450,6 +1450,7 @@ class _LandingPageState extends State { appointmentNoVida: order.appointmentNo ?? 0, orderNo: order.orderNo ?? 0, projectID: order.projectID ?? 0, + clinicID: 0, projectName: order.projectName ?? "", setupID: order.setupID ?? "", ), diff --git a/lib/presentation/todo_section/ancillary_order_payment_page.dart b/lib/presentation/todo_section/ancillary_order_payment_page.dart index 80b79a88..36a2d7c5 100644 --- a/lib/presentation/todo_section/ancillary_order_payment_page.dart +++ b/lib/presentation/todo_section/ancillary_order_payment_page.dart @@ -14,6 +14,8 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart'; @@ -36,6 +38,7 @@ class AncillaryOrderPaymentPage extends StatefulWidget { final int appointmentNoVida; final int orderNo; final int projectID; + final int clinicID; final String setupID; final List selectedProcedures; final num totalAmount; @@ -46,6 +49,7 @@ class AncillaryOrderPaymentPage extends StatefulWidget { required this.appointmentNoVida, required this.orderNo, required this.projectID, + required this.clinicID, required this.setupID, required this.selectedProcedures, required this.totalAmount, @@ -60,16 +64,38 @@ class _AncillaryOrderPaymentPageState extends State { late AppState appState; late TodoSectionViewModel todoSectionViewModel; late PayTabsViewModel paytabsViewModel; + late MyAppointmentsViewModel myAppointmentsViewModel; + late HabibWalletViewModel habibWalletVM; MyInAppBrowser? browser; String selectedPaymentMethod = ""; String transID = ""; + bool isShowTamara = false; + String tamaraPaymentStatus = ""; + String tamaraOrderID = ""; + @override void initState() { scheduleMicrotask(() { payfortViewModel.initPayfortViewModel(); payfortViewModel.setIsApplePayConfigurationLoading(false); + + myAppointmentsViewModel.getTamaraInstallmentsDetails().then((val) { + // if (myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel != null && myAppointmentsViewModel.patientAppointmentShareResponseModel != null) { + if (myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel != null && + myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel?.minLimit?.amount != null && + myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel?.maxLimit?.amount != null) { + if (widget.totalAmount >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && + widget.totalAmount <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) { + if (mounted) { + setState(() { + isShowTamara = true; + }); + } + } + } + }); }); super.initState(); } @@ -80,6 +106,8 @@ class _AncillaryOrderPaymentPageState extends State { todoSectionViewModel = Provider.of(context); payfortViewModel = Provider.of(context); paytabsViewModel = Provider.of(context, listen: false); + myAppointmentsViewModel = Provider.of(context); + habibWalletVM = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, @@ -364,6 +392,85 @@ class _AncillaryOrderPaymentPageState extends State { } } }), + SizedBox(height: 16.h), + isShowTamara + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildImgWithAssets(icon: AppAssets.tamaraEng, width: 72.h, height: 25.h).toShimmer2(isShow: false), + SizedBox(height: 16.h), + LocaleKeys.tamara.tr(context: context).toText16(isBold: true).toShimmer2(isShow: false), + ], + ), + SizedBox(width: 8.h), + const Spacer(), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ).toShimmer2(isShow: false), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + selectedPaymentMethod = "TAMARA"; + transID = Utils.getAppointmentTransID( + widget.projectID, + widget.clinicID, + widget.appointmentNoVida, + ); + if (appState.isPaytabsEnabled) { + paytabsViewModel.setAlternatePaymentConfiguration("Ancillary Order Payment", widget.totalAmount); + paytabsViewModel.startAlternatePayment( + liveServiceID: "0", + appointmentDate: DateUtil.convertDateToString(widget.appointmentDate!), + appointmentNo: widget.appointmentNoVida.toString(), + doctorID: "0", + transactionID: transID, + amount: widget.totalAmount.toString(), + fileNumber: appState.getAuthenticatedUser()!.patientId.toString(), + serviceID: "2", + projectID: widget.projectID.toString(), + isSchedule: false, + onSuccess: (PaytabsTransactionResponseModel transactionData) async { + tamaraOrderID = transactionData.cartID!; + // transID = transactionData.transactionReference!; + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); + await payfortViewModel.updateTamaraRequestStatus(responseMessage: "success", status: "14", clientRequestID: transID, tamaraOrderID: tamaraOrderID); + _createAdvancePayment( + paymentAmount: widget.totalAmount, + paymentReference: transactionData.transactionReference!, + paymentMethod: selectedPaymentMethod, + ); + }, + onError: (err) async { + await payfortViewModel.updateTamaraRequestStatus(responseMessage: "Failed", status: "00", clientRequestID: transID, tamaraOrderID: tamaraOrderID); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + _openPaymentURL("tamara"); + } + }) + : SizedBox.shrink(), ], ), ), diff --git a/lib/presentation/todo_section/ancillary_procedures_details_page.dart b/lib/presentation/todo_section/ancillary_procedures_details_page.dart index c34031f4..24a6d97b 100644 --- a/lib/presentation/todo_section/ancillary_procedures_details_page.dart +++ b/lib/presentation/todo_section/ancillary_procedures_details_page.dart @@ -29,6 +29,7 @@ class AncillaryOrderDetailsList extends StatefulWidget { final int appointmentNoVida; final int orderNo; final int projectID; + final int clinicID; final String projectName; final String setupID; @@ -37,6 +38,7 @@ class AncillaryOrderDetailsList extends StatefulWidget { required this.appointmentNoVida, required this.orderNo, required this.projectID, + required this.clinicID, required this.projectName, required this.setupID, }); @@ -682,6 +684,7 @@ class _AncillaryOrderDetailsListState extends State { appointmentNoVida: widget.appointmentNoVida, orderNo: widget.orderNo, projectID: widget.projectID, + clinicID: widget.clinicID, selectedProcedures: selectedProcedures, totalAmount: _getTotalAmount(), appointmentDate: orderData.appointmentDate, diff --git a/lib/presentation/todo_section/todo_page.dart b/lib/presentation/todo_section/todo_page.dart index d669668a..2f1061d2 100644 --- a/lib/presentation/todo_section/todo_page.dart +++ b/lib/presentation/todo_section/todo_page.dart @@ -83,7 +83,8 @@ class _ToDoPageState extends State { appointmentNoVida: order.appointmentNo ?? 0, orderNo: order.orderNo ?? 0, projectID: order.projectID ?? 0, - projectName: order.projectName ?? "", + clinicID: order.clinicID ?? 0, + projectName: order.projectName ?? "", setupID: order.setupID ?? "0", ), ), From 343b5655f8fa404a8313c14cc34e998d42c1c6b6 Mon Sep 17 00:00:00 2001 From: taha Date: Sun, 23 Aug 2026 15:46:43 +0300 Subject: [PATCH 02/15] refactor: streamline calculator result handling and UI updates(Aamir changes) --- .../book_appointment/select_clinic_page.dart | 788 ++++++++---------- .../health_calculator_detailed_page.dart | 197 +++-- 2 files changed, 458 insertions(+), 527 deletions(-) diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index 785ebb62..5f76ab31 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -89,13 +89,15 @@ class _SelectClinicPageState extends State { 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")); + if (widget.calculatedResult != null && widget.calculatedResult is Map) { + 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, @@ -132,13 +134,13 @@ class _SelectClinicPageState extends State { focusNode: textFocusNode, suffix: searchEditingController.text.isNotEmpty ? GestureDetector( - onTap: () { - searchEditingController.clear(); - bookAppointmentsViewModel.filterClinics(""); - textFocusNode.unfocus(); - }, - child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown), - ) + onTap: () { + searchEditingController.clear(); + bookAppointmentsViewModel.filterClinics(""); + textFocusNode.unfocus(); + }, + child: Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown), + ) : null, onChange: (value) { bookAppointmentsViewModel.filterClinics(value!); @@ -150,90 +152,90 @@ class _SelectClinicPageState extends State { ), bookAppointmentsVM.isLiveCareSchedule ? ListView.separated( - padding: EdgeInsets.only(top: 24.h), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: bookAppointmentsVM.isClinicsListLoading - ? 5 - : bookAppointmentsVM.liveCareClinicsList.isNotEmpty - ? bookAppointmentsVM.liveCareClinicsList.length - : 1, + padding: EdgeInsets.only(top: 24.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: bookAppointmentsVM.isClinicsListLoading + ? 5 + : bookAppointmentsVM.liveCareClinicsList.isNotEmpty + ? bookAppointmentsVM.liveCareClinicsList.length + : 1, - // bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.liveCareClinicsList.length, + // bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.liveCareClinicsList.length, - itemBuilder: (context, index) { - return bookAppointmentsVM.isClinicsListLoading - ? ClinicCard( - bookAppointmentsVM: bookAppointmentsVM, - liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), - clinicsListResponseModel: GetClinicsListResponseModel(), - isLoading: bookAppointmentsVM.isClinicsListLoading, - ) - : AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: AnimatedContainer( - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), - child: bookAppointmentsVM.liveCareClinicsList.isNotEmpty - ? ClinicCard( - bookAppointmentsVM: bookAppointmentsVM, - liveCareClinicsResponseModel: bookAppointmentsVM.liveCareClinicsList[index], - clinicsListResponseModel: GetClinicsListResponseModel(), - isLoading: bookAppointmentsVM.isClinicsListLoading, - ).onPress(() { - onLiveCareClinicSelected(bookAppointmentsVM.liveCareClinicsList[index]); - }) - : Utils.getNoDataWidget(context), - ), - ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ) + itemBuilder: (context, index) { + return bookAppointmentsVM.isClinicsListLoading + ? ClinicCard( + bookAppointmentsVM: bookAppointmentsVM, + liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), + clinicsListResponseModel: GetClinicsListResponseModel(), + isLoading: bookAppointmentsVM.isClinicsListLoading, + ) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: bookAppointmentsVM.liveCareClinicsList.isNotEmpty + ? ClinicCard( + bookAppointmentsVM: bookAppointmentsVM, + liveCareClinicsResponseModel: bookAppointmentsVM.liveCareClinicsList[index], + clinicsListResponseModel: GetClinicsListResponseModel(), + isLoading: bookAppointmentsVM.isClinicsListLoading, + ).onPress(() { + onLiveCareClinicSelected(bookAppointmentsVM.liveCareClinicsList[index]); + }) + : Utils.getNoDataWidget(context), + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ) : ListView.separated( - padding: EdgeInsets.only(top: 24.h), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.filteredClinicsList.length, - itemBuilder: (context, index) { - return bookAppointmentsVM.isClinicsListLoading - ? ClinicCard( - bookAppointmentsVM: bookAppointmentsVM, - liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), - clinicsListResponseModel: GetClinicsListResponseModel(), - isLoading: bookAppointmentsVM.isClinicsListLoading, - ) - : AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: AnimatedContainer( - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), - child: ClinicCard( - bookAppointmentsVM: bookAppointmentsVM, - liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), - clinicsListResponseModel: bookAppointmentsVM.filteredClinicsList[index], - isLoading: bookAppointmentsVM.isClinicsListLoading, - ).onPress(() { - onClinicSelected(bookAppointmentsVM.filteredClinicsList[index]); - }), - ), - ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + padding: EdgeInsets.only(top: 24.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: bookAppointmentsVM.isClinicsListLoading ? 5 : bookAppointmentsVM.filteredClinicsList.length, + itemBuilder: (context, index) { + return bookAppointmentsVM.isClinicsListLoading + ? ClinicCard( + bookAppointmentsVM: bookAppointmentsVM, + liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), + clinicsListResponseModel: GetClinicsListResponseModel(), + isLoading: bookAppointmentsVM.isClinicsListLoading, + ) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: ClinicCard( + bookAppointmentsVM: bookAppointmentsVM, + liveCareClinicsResponseModel: GetLiveCareClinicsResponseModel(), + clinicsListResponseModel: bookAppointmentsVM.filteredClinicsList[index], + isLoading: bookAppointmentsVM.isClinicsListLoading, + ).onPress(() { + onClinicSelected(bookAppointmentsVM.filteredClinicsList[index]); + }), + ), + ), ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), ], ); }), @@ -272,14 +274,17 @@ class _SelectClinicPageState extends State { } Widget resultWidget({required HealthCalculatorsTypeEnum type, dynamic calculatedResult}) { + if (calculatedResult == null || calculatedResult is! Map) { + return const SizedBox.shrink(); + } AppState appState = getIt(); return Column( children: [ if (type == HealthCalculatorsTypeEnum.bmi) ...[ if (calculatedResult["bmiCategory"] == "Normal") ...[ calorieWidget( - title: calculatedResult["bmiCategory"], - calories: calculatedResult["bmiResult"], + title: calculatedResult["bmiCategory"]?.toString() ?? "", + calories: calculatedResult["bmiResult"] ?? 0, description: LocaleKeys.bmiNormalDescription.tr(), color: Colors.green, icon: Icons.scale, @@ -288,8 +293,8 @@ class _SelectClinicPageState extends State { SizedBox(height: 16.h), ] else if (calculatedResult["bmiCategory"] == "Underweight") ...[ calorieWidget( - title: calculatedResult["bmiCategory"], - calories: calculatedResult["bmiResult"], + title: calculatedResult["bmiCategory"]?.toString() ?? "", + calories: calculatedResult["bmiResult"] ?? 0, description: LocaleKeys.bmiUnderweightDescription.tr(), color: Colors.orange, hide: true, @@ -298,8 +303,8 @@ class _SelectClinicPageState extends State { SizedBox(height: 16.h), ] else if (calculatedResult["bmiCategory"] == "Overweight") ...[ calorieWidget( - title: calculatedResult["bmiCategory"], - calories: calculatedResult["bmiResult"], + title: calculatedResult["bmiCategory"]?.toString() ?? "", + calories: calculatedResult["bmiResult"] ?? 0, description: LocaleKeys.bmiOverweightDescription.tr(), color: Colors.red, hide: true, @@ -309,8 +314,8 @@ class _SelectClinicPageState extends State { ] else if (type == HealthCalculatorsTypeEnum.calories) ...[ if (calculatedResult["calorieRange"] == "Normal") ...[ calorieWidget( - title: calculatedResult["calorieRange"], - calories: calculatedResult["calories"], + title: calculatedResult["calorieRange"]?.toString() ?? "", + calories: calculatedResult["calories"] ?? 0, description: LocaleKeys.caloriesMaintenanceDescription.tr(), color: Colors.green, icon: Icons.scale, @@ -318,8 +323,8 @@ class _SelectClinicPageState extends State { SizedBox(height: 16.h), ] else if (calculatedResult["calorieRange"] == "Low") ...[ calorieWidget( - title: calculatedResult["calorieRange"], - calories: calculatedResult["calories"], + title: calculatedResult["calorieRange"]?.toString() ?? "", + calories: calculatedResult["calories"] ?? 0, description: LocaleKeys.caloriesModerateDeficitDescription.tr(), color: Colors.orange, icon: Icons.trending_down, @@ -327,8 +332,8 @@ class _SelectClinicPageState extends State { SizedBox(height: 16.h), ] else if (calculatedResult["calorieRange"] == "High") ...[ calorieWidget( - title: calculatedResult["calorieRange"], - calories: calculatedResult["calories"], + title: calculatedResult["calorieRange"]?.toString() ?? "", + calories: calculatedResult["calories"] ?? 0, description: LocaleKeys.caloriesAggressiveDeficitDescription.tr(), color: Colors.red, icon: Icons.warning_rounded, @@ -337,8 +342,8 @@ class _SelectClinicPageState extends State { ] else if (type == HealthCalculatorsTypeEnum.bodyFat) ...[ if (calculatedResult["fatCategory"] == "Essential Fat" || calculatedResult["fatCategory"] == "Athletes") ...[ calorieWidget( - title: calculatedResult["fatCategory"], - calories: calculatedResult["fatPercentage"], + title: calculatedResult["fatCategory"]?.toString() ?? "", + calories: calculatedResult["fatPercentage"] ?? 0, description: LocaleKeys.bodyFatEssentialDescription.tr(), color: Colors.green, hide: true, @@ -347,8 +352,8 @@ class _SelectClinicPageState extends State { SizedBox(height: 16.h), ] else if (calculatedResult["fatCategory"] == "Fitness") ...[ calorieWidget( - title: calculatedResult["fatCategory"], - calories: calculatedResult["fatPercentage"], + title: calculatedResult["fatCategory"]?.toString() ?? "", + calories: calculatedResult["fatPercentage"] ?? 0, description: LocaleKeys.bodyFatFitnessDescription.tr(), color: Colors.green, hide: true, @@ -357,8 +362,8 @@ class _SelectClinicPageState extends State { SizedBox(height: 16.h), ] else if (calculatedResult["fatCategory"] == "Average" || calculatedResult["fatCategory"] == "Acceptable") ...[ calorieWidget( - title: calculatedResult["fatCategory"], - calories: calculatedResult["fatPercentage"], + title: calculatedResult["fatCategory"]?.toString() ?? "", + calories: calculatedResult["fatPercentage"] ?? 0, description: LocaleKeys.bodyFatAverageDescription.tr(), color: Colors.orange, hide: true, @@ -367,8 +372,8 @@ class _SelectClinicPageState extends State { SizedBox(height: 16.h), ] else if (calculatedResult["fatCategory"] == "Obese" || calculatedResult["fatCategory"] == "Overweight") ...[ calorieWidget( - title: calculatedResult["fatCategory"], - calories: calculatedResult["fatPercentage"], + title: calculatedResult["fatCategory"]?.toString() ?? "", + calories: calculatedResult["fatPercentage"] ?? 0, description: LocaleKeys.bodyFatObeseDescription.tr(), color: Colors.red, hide: true, @@ -379,8 +384,8 @@ class _SelectClinicPageState extends State { ] else if (type == HealthCalculatorsTypeEnum.bmr) ...[ if (calculatedResult["bmrRange"] == "Normal") ...[ calorieWidget( - title: calculatedResult["bmrRange"], - calories: calculatedResult["bmr"], + title: calculatedResult["bmrRange"]?.toString() ?? "", + calories: calculatedResult["bmr"] ?? 0, description: LocaleKeys.bmrNormalDescription.tr(), color: Colors.green, hide: true, @@ -389,8 +394,8 @@ class _SelectClinicPageState extends State { SizedBox(height: 16.h), ] else if (calculatedResult["bmrRange"] == "Low") ...[ calorieWidget( - title: calculatedResult["bmrRange"], - calories: calculatedResult["bmr"], + title: calculatedResult["bmrRange"]?.toString() ?? "", + calories: calculatedResult["bmr"] ?? 0, description: LocaleKeys.low.tr(), color: Colors.orange, hide: true, @@ -399,8 +404,8 @@ class _SelectClinicPageState extends State { SizedBox(height: 16.h), ] else if (calculatedResult["bmrRange"] == "High") ...[ calorieWidget( - title: calculatedResult["bmrRange"], - calories: calculatedResult["bmr"], + title: calculatedResult["bmrRange"]?.toString() ?? "", + calories: calculatedResult["bmr"] ?? 0, description: LocaleKeys.bmrHighDescription.tr(), color: Colors.red, hide: true, @@ -410,8 +415,8 @@ class _SelectClinicPageState extends State { ] else if (type == HealthCalculatorsTypeEnum.idealBodyWeight) ...[ if (calculatedResult["status"] == "Normal") ...[ calorieWidget( - title: calculatedResult["status"], - calories: calculatedResult["ibw"], + title: calculatedResult["status"]?.toString() ?? "", + calories: calculatedResult["ibw"] ?? 0, hide: true, description: (() { final diff = calculatedResult["difference"]; @@ -432,8 +437,8 @@ class _SelectClinicPageState extends State { SizedBox(height: 16.h), ] else if (calculatedResult["status"] == "Underweight") ...[ calorieWidget( - title: calculatedResult["status"], - calories: calculatedResult["ibw"], + title: calculatedResult["status"]?.toString() ?? "", + calories: calculatedResult["ibw"] ?? 0, hide: true, description: (() { final diff = calculatedResult["difference"]; @@ -448,8 +453,8 @@ class _SelectClinicPageState extends State { SizedBox(height: 16.h), ] else if (calculatedResult["status"] == "Overweight") ...[ calorieWidget( - title: calculatedResult["status"], - calories: calculatedResult["ibw"], + title: calculatedResult["status"]?.toString() ?? "", + calories: calculatedResult["ibw"] ?? 0, hide: true, description: (() { final diff = calculatedResult["difference"]; @@ -463,151 +468,41 @@ class _SelectClinicPageState extends State { ), ] ] else if (type == HealthCalculatorsTypeEnum.crabsProteinFat) ...[ - if (calculatedResult["dietType"] == "Very Low Crabs") ...[ - calorieWidget( - title: calculatedResult["dietType"].toString(), - calories: calculatedResult["totalCalories"], - description: (() { - final total = calculatedResult["totalCalories"]; - final carbsG = calculatedResult["carbsGrams"]; - final carbsC = calculatedResult["carbsCalories"]; - final protG = calculatedResult["proteinGrams"]; - final protC = calculatedResult["proteinCalories"]; - final fatG = calculatedResult["fatGrams"]; - final fatC = calculatedResult["fatCalories"]; - final base = - '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.'; - // if (total is num) { - // return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown: Carbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).'; - // } - return base; - })(), - chipsItems: [ - ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'), - ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'), - ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'), - ], - color: Colors.orange, - hide: true, - icon: Icons.trending_down, - ), - SizedBox(height: 16.h), - ] else if (calculatedResult["dietType"] == "Low Crabs") ...[ - calorieWidget( - title: calculatedResult["dietType"].toString(), - calories: calculatedResult["totalCalories"], - description: (() { - final total = calculatedResult["totalCalories"]; - final carbsG = calculatedResult["carbsGrams"]; - final carbsC = calculatedResult["carbsCalories"]; - final protG = calculatedResult["proteinGrams"]; - final protC = calculatedResult["proteinCalories"]; - final fatG = calculatedResult["fatGrams"]; - final fatC = calculatedResult["fatCalories"]; - final base = - '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.'; - // if (total is num) { - // return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).'; - // } - return base; - })(), - chipsItems: [ - ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'), - ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'), - ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'), - ], - color: Colors.orange, - hide: true, - icon: Icons.trending_down, - ), - SizedBox(height: 16.h), - ] else if (calculatedResult["dietType"] == "Moderate Crabs") ...[ + if (calculatedResult["dietType"] == "Very Low Crabs" || + calculatedResult["dietType"] == "Low Crabs" || + calculatedResult["dietType"] == "Moderate Crabs" || + calculatedResult["dietType"] == "USDA Guidelines " || + calculatedResult["dietType"] == "Zone Diet") ...[ calorieWidget( - title: calculatedResult["dietType"].toString(), - calories: calculatedResult["totalCalories"], + title: calculatedResult["dietType"]?.toString() ?? "", + calories: calculatedResult["totalCalories"] ?? 0, description: (() { - final total = calculatedResult["totalCalories"]; - final carbsG = calculatedResult["carbsGrams"]; - final carbsC = calculatedResult["carbsCalories"]; - final protG = calculatedResult["proteinGrams"]; - final protC = calculatedResult["proteinCalories"]; - final fatG = calculatedResult["fatGrams"]; - final fatC = calculatedResult["fatCalories"]; - final base = - '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.'; - // if (total is num) { - // return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).'; - // } - return base; - })(), - chipsItems: [ - ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'), - ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'), - ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'), - ], - color: Colors.green, - hide: true, - icon: Icons.scale, - ), - SizedBox(height: 16.h), - ] else if (calculatedResult["dietType"] == "USDA Guidelines ") ...[ - calorieWidget( - title: calculatedResult["dietType"].toString(), - calories: calculatedResult["totalCalories"], - description: (() { - final total = calculatedResult["totalCalories"]; - final carbsG = calculatedResult["carbsGrams"]; - final carbsC = calculatedResult["carbsCalories"]; - final protG = calculatedResult["proteinGrams"]; - final protC = calculatedResult["proteinCalories"]; - final fatG = calculatedResult["fatGrams"]; - final fatC = calculatedResult["fatCalories"]; - final base = - '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.'; - // if (total is num) { - // return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).'; - // } - - return base; + final dt = calculatedResult["dietType"]; + if (dt == "Very Low Crabs") { + return '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.'; + } else if (dt == "Low Crabs") { + return '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.'; + } else if (dt == "Moderate Crabs") { + return '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.'; + } else if (dt == "USDA Guidelines ") { + return '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.'; + } else { + return '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.'; + } })(), chipsItems: [ - ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'), - ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'), - ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'), + if (calculatedResult["carbsGrams"] != null && calculatedResult["carbsCalories"] != null) + ('Carbohydrates: ' + ((calculatedResult["carbsGrams"] is num) ? (calculatedResult["carbsGrams"] as num).toStringAsFixed(0) : calculatedResult["carbsGrams"].toString()) + 'g = ' + ((calculatedResult["carbsCalories"] is num) ? (calculatedResult["carbsCalories"] as num).toStringAsFixed(0) : calculatedResult["carbsCalories"].toString()) + ' kcal'), + if (calculatedResult["proteinGrams"] != null && calculatedResult["proteinCalories"] != null) + ('Protein: ' + ((calculatedResult["proteinGrams"] is num) ? (calculatedResult["proteinGrams"] as num).toStringAsFixed(0) : calculatedResult["proteinGrams"].toString()) + 'g = ' + ((calculatedResult["proteinCalories"] is num) ? (calculatedResult["proteinCalories"] as num).toStringAsFixed(0) : calculatedResult["proteinCalories"].toString()) + ' kcal'), + if (calculatedResult["fatGrams"] != null && calculatedResult["fatCalories"] != null) + ('Fat: ' + ((calculatedResult["fatGrams"] is num) ? (calculatedResult["fatGrams"] as num).toStringAsFixed(0) : calculatedResult["fatGrams"].toString()) + 'g = ' + ((calculatedResult["fatCalories"] is num) ? (calculatedResult["fatCalories"] as num).toStringAsFixed(0) : calculatedResult["fatCalories"].toString()) + ' kcal'), ], - color: Colors.green, + color: (calculatedResult["dietType"] == "Moderate Crabs" || calculatedResult["dietType"] == "USDA Guidelines ") ? Colors.green : Colors.orange, hide: true, - icon: Icons.scale, + icon: (calculatedResult["dietType"] == "Moderate Crabs" || calculatedResult["dietType"] == "USDA Guidelines ") ? Icons.scale : (calculatedResult["dietType"] == "Zone Diet" ? Icons.warning_rounded : Icons.trending_down), ), SizedBox(height: 16.h), - ] else if (calculatedResult["dietType"] == "Zone Diet") ...[ - calorieWidget( - title: calculatedResult["dietType"].toString(), - calories: calculatedResult["totalCalories"], - description: (() { - final total = calculatedResult["totalCalories"]; - final carbsG = calculatedResult["carbsGrams"]; - final carbsC = calculatedResult["carbsCalories"]; - final protG = calculatedResult["proteinGrams"]; - final protC = calculatedResult["proteinCalories"]; - final fatG = calculatedResult["fatGrams"]; - final fatC = calculatedResult["fatCalories"]; - final base = - '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.'; - // if (total is num) { - // return '$base\n\nTotal: ${total.toStringAsFixed(0)} kcal.\n\nBreakdown:\nCarbs ${carbsG is num ? carbsG.toStringAsFixed(0) + " g" : "N/A"} (${carbsC is num ? carbsC.toStringAsFixed(0) + " kcal" : "N/A"}),\nProtein ${protG is num ? protG.toStringAsFixed(0) + " g" : "N/A"} (${protC is num ? protC.toStringAsFixed(0) + " kcal" : "N/A"}),\nFat ${fatG is num ? fatG.toStringAsFixed(1) + " g" : "N/A"} (${fatC is num ? fatC.toStringAsFixed(0) + " kcal" : "N/A"}).'; - // } - return base; - })(), - chipsItems: [ - ('Carbohydrates: ' + calculatedResult["carbsGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["carbsCalories"].toStringAsFixed(0) + ' kcal'), - ('Protein: ' + calculatedResult["proteinGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["proteinCalories"].toStringAsFixed(0) + ' kcal'), - ('Fat: ' + calculatedResult["fatGrams"].toStringAsFixed(0) + 'g = ' + calculatedResult["fatCalories"].toStringAsFixed(0) + ' kcal'), - ], - color: Colors.orange, - hide: true, - icon: Icons.warning_rounded, - ), ] ] else if (type == HealthCalculatorsTypeEnum.ovulation) ...[ Column( @@ -626,21 +521,23 @@ class _SelectClinicPageState extends State { SizedBox( width: 8.w, ), - calculatedResult["lastPeriodDate"].toString().toText16(color: AppColors.successColor, isBold: true), + (calculatedResult["lastPeriodDate"] ?? "").toString().toText16(color: AppColors.successColor, isBold: true), ], ), ], ), - SizedBox(height: 8.h), - CustomChipWidget( - height: 30.h, - chipType: ChipTypeEnum.alert, - backgroundColor: AppColors.lightGrayBGColor, - chipText: calculatedResult["lastPeriodDay"].toString(), - iconAsset: null, - isShowBorder: false, - borderRadius: 8.h, - textColor: AppColors.textColor), + if (calculatedResult["lastPeriodDay"] != null) ...[ + SizedBox(height: 8.h), + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: calculatedResult["lastPeriodDay"].toString(), + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), + ], SizedBox(height: 16.h), Row( children: [ @@ -648,19 +545,21 @@ class _SelectClinicPageState extends State { SizedBox( width: 8.w, ), - calculatedResult["ovulationDate"].toString().toText16(color: AppColors.successColor, isBold: true), + (calculatedResult["ovulationDate"] ?? "").toString().toText16(color: AppColors.successColor, isBold: true), ], ), - SizedBox(height: 8.h), - CustomChipWidget( - height: 30.h, - chipType: ChipTypeEnum.alert, - backgroundColor: AppColors.lightGrayBGColor, - chipText: calculatedResult["ovulationDay"].toString(), - iconAsset: null, - isShowBorder: false, - borderRadius: 8.h, - textColor: AppColors.textColor), + if (calculatedResult["ovulationDay"] != null) ...[ + SizedBox(height: 8.h), + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: calculatedResult["ovulationDay"].toString(), + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), + ], SizedBox(height: 16.h), appState.isArabic() ? "فترة الخصوبة".toText16(color: AppColors.textColor, isBold: true) : "Fertility Window".toText16(color: AppColors.textColor, isBold: true), SizedBox(height: 8.h), @@ -670,7 +569,7 @@ class _SelectClinicPageState extends State { SizedBox( width: 8.w, ), - calculatedResult["fertileWindowStart"].toString().toText13(color: AppColors.successColor, isBold: true), + (calculatedResult["fertileWindowStart"] ?? "").toString().toText13(color: AppColors.successColor, isBold: true), SizedBox( width: 4.w, ), @@ -678,15 +577,15 @@ class _SelectClinicPageState extends State { SizedBox( width: 4.w, ), - calculatedResult["fertileWindowEnd"].toString().toText13(color: AppColors.successColor, isBold: true), + (calculatedResult["fertileWindowEnd"] ?? "").toString().toText13(color: AppColors.successColor, isBold: true), ], ), SizedBox(height: 8.h), appState.isArabic() ? "هذه هي فترة خصوبتك. يساعد الحفاظ على نطاق صحي من السعرات الحرارية في دعم جسمك خلال هذه الفترة، مع مراعاة معدل الأيض الأساسي ومستوى نشاطك. مؤشر كتلة جسمك ضمن النطاق الصحي." - .toText12(height: 1.5, color: AppColors.textColorLight, isBold: true) + .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) + .toText12(height: 1.5, color: AppColors.textColorLight, isBold: true) ], ), ), @@ -745,156 +644,166 @@ class _SelectClinicPageState extends State { children: [ Utils.buildSvgWithAssets(icon: AppAssets.pregnancyDueDateAccordion, width: 30.h, height: 30.h), SizedBox(width: 8.w), - calculatedResult["dueDate"].toString().toText16(color: AppColors.successColor, isBold: true), + (calculatedResult["dueDate"] ?? "").toString().toText16(color: AppColors.successColor, isBold: true), ], ), Expanded(child: SizedBox(width: 8.w)), - CustomChipWidget( - height: 30.h, - chipType: ChipTypeEnum.alert, - backgroundColor: AppColors.lightGrayBGColor, - chipText: calculatedResult["dueDateDay"].toString(), - iconAsset: null, - isShowBorder: false, - borderRadius: 8.h, - textColor: AppColors.textColor), + if (calculatedResult["dueDateDay"] != null) + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: calculatedResult["dueDateDay"].toString(), + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), ], ), ], ), ), - SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), - padding: EdgeInsets.all(16.w), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - "First Trimester".toText16(color: AppColors.textColor, isBold: true), - Expanded(child: SizedBox(width: 8.h)), - CustomChipWidget( - height: 30.h, - chipType: ChipTypeEnum.alert, - backgroundColor: AppColors.lightGrayBGColor, - chipText: "${calculatedResult["firstTrimester"]["weeks"]} Weeks", - iconAsset: null, - isShowBorder: false, - borderRadius: 8.h, - textColor: AppColors.textColor), - ], - ), - SizedBox(height: 8.h), - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.fertileAccordion, width: 30.h, height: 30.h), - SizedBox( - width: 8.w, - ), - calculatedResult["firstTrimester"]["start"].toString().toText13(color: AppColors.successColor, isBold: true), - SizedBox( - width: 4.w, - ), - "-".toText16(isBold: true, color: AppColors.successColor), - SizedBox( - width: 4.w, - ), - calculatedResult["firstTrimester"]["end"].toString().toText13(color: AppColors.successColor, isBold: true), - ], - ), - SizedBox(height: 8.h), - ], + if (calculatedResult["firstTrimester"] is Map) ...[ + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + "First Trimester".toText16(color: AppColors.textColor, isBold: true), + Expanded(child: SizedBox(width: 8.h)), + if (calculatedResult["firstTrimester"]?["weeks"] != null) + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: "${calculatedResult["firstTrimester"]["weeks"]} Weeks", + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.fertileAccordion, width: 30.h, height: 30.h), + SizedBox( + width: 8.w, + ), + (calculatedResult["firstTrimester"]?["start"] ?? "").toString().toText13(color: AppColors.successColor, isBold: true), + SizedBox( + width: 4.w, + ), + "-".toText16(isBold: true, color: AppColors.successColor), + SizedBox( + width: 4.w, + ), + (calculatedResult["firstTrimester"]?["end"] ?? "").toString().toText13(color: AppColors.successColor, isBold: true), + ], + ), + SizedBox(height: 8.h), + ], + ), ), - ), - SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), - padding: EdgeInsets.all(16.w), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - "Second Trimester".toText16(color: AppColors.textColor, isBold: true), - Expanded(child: SizedBox(width: 8.h)), - CustomChipWidget( - height: 30.h, - chipType: ChipTypeEnum.alert, - backgroundColor: AppColors.lightGrayBGColor, - chipText: "${calculatedResult["secondTrimester"]["weeks"]} Weeks", - iconAsset: null, - isShowBorder: false, - borderRadius: 8.h, - textColor: AppColors.textColor), - ], - ), - SizedBox(height: 8.h), - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.ovulationAccordion, width: 30.h, height: 30.h), - SizedBox( - width: 8.w, - ), - calculatedResult["secondTrimester"]["start"].toString().toText13(color: AppColors.successColor, isBold: true), - SizedBox( - width: 4.w, - ), - "-".toText16(isBold: true, color: AppColors.successColor), - SizedBox( - width: 4.w, - ), - calculatedResult["secondTrimester"]["end"].toString().toText13(color: AppColors.successColor, isBold: true), - ], - ), - SizedBox(height: 8.h), - ], + ], + if (calculatedResult["secondTrimester"] is Map) ...[ + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + "Second Trimester".toText16(color: AppColors.textColor, isBold: true), + Expanded(child: SizedBox(width: 8.h)), + if (calculatedResult["secondTrimester"]?["weeks"] != null) + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: "${calculatedResult["secondTrimester"]["weeks"]} Weeks", + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.ovulationAccordion, width: 30.h, height: 30.h), + SizedBox( + width: 8.w, + ), + (calculatedResult["secondTrimester"]?["start"] ?? "").toString().toText13(color: AppColors.successColor, isBold: true), + SizedBox( + width: 4.w, + ), + "-".toText16(isBold: true, color: AppColors.successColor), + SizedBox( + width: 4.w, + ), + (calculatedResult["secondTrimester"]?["end"] ?? "").toString().toText13(color: AppColors.successColor, isBold: true), + ], + ), + SizedBox(height: 8.h), + ], + ), ), - ), - SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), - padding: EdgeInsets.all(16.w), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - "Third Trimester".toText16(color: AppColors.textColor, isBold: true), - Expanded(child: SizedBox(width: 8.h)), - CustomChipWidget( - height: 30.h, - chipType: ChipTypeEnum.alert, - backgroundColor: AppColors.lightGrayBGColor, - chipText: "${calculatedResult["thirdTrimester"]["weeks"]} Weeks", - iconAsset: null, - isShowBorder: false, - borderRadius: 8.h, - textColor: AppColors.textColor), - ], - ), - SizedBox(height: 8.h), - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.pregnancyDayAccordion, width: 30.h, height: 30.h), - SizedBox( - width: 8.w, - ), - calculatedResult["thirdTrimester"]["start"].toString().toText13(color: AppColors.successColor, isBold: true), - SizedBox( - width: 4.w, - ), - "-".toText16(isBold: true, color: AppColors.successColor), - SizedBox( - width: 4.w, - ), - calculatedResult["thirdTrimester"]["end"].toString().toText13(color: AppColors.successColor, isBold: true), - ], - ), - SizedBox(height: 8.h), - ], + ], + if (calculatedResult["thirdTrimester"] is Map) ...[ + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + "Third Trimester".toText16(color: AppColors.textColor, isBold: true), + Expanded(child: SizedBox(width: 8.h)), + if (calculatedResult["thirdTrimester"]?["weeks"] != null) + CustomChipWidget( + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: "${calculatedResult["thirdTrimester"]["weeks"]} Weeks", + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.pregnancyDayAccordion, width: 30.h, height: 30.h), + SizedBox( + width: 8.w, + ), + (calculatedResult["thirdTrimester"]?["start"] ?? "").toString().toText13(color: AppColors.successColor, isBold: true), + SizedBox( + width: 4.w, + ), + "-".toText16(isBold: true, color: AppColors.successColor), + SizedBox( + width: 4.w, + ), + (calculatedResult["thirdTrimester"]?["end"] ?? "").toString().toText13(color: AppColors.successColor, isBold: true), + ], + ), + SizedBox(height: 8.h), + ], + ), ), - ), + ], ], ) ], @@ -921,6 +830,7 @@ class _SelectClinicPageState extends State { } Widget calorieWidget({required String title, required dynamic calories, required String description, required Color color, required IconData icon, bool hide = false, List? chipsItems}) { + final caloriesNum = (calories is num) ? calories : (double.tryParse(calories?.toString() ?? '') ?? 0); return Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, customBorder: BorderRadius.all(Radius.circular(24.r))), padding: EdgeInsets.all(16.w), @@ -932,7 +842,7 @@ class _SelectClinicPageState extends State { children: [ Row( children: [ - ('${calories.toStringAsFixed(0)}').toText32(color: getTextColor(title), isBold: true), + ('${caloriesNum.toStringAsFixed(0)}').toText32(color: getTextColor(title), isBold: true), SizedBox(width: 10.w), hide ? SizedBox() : 'kcal/day'.toText12(color: AppColors.inputLabelTextColor, isBold: true) ], @@ -947,14 +857,14 @@ class _SelectClinicPageState extends State { runSpacing: 4.0, // Vertical space between rows of chips children: chipsItems.map((item) { return CustomChipWidget( - height: 30.h, - chipType: ChipTypeEnum.alert, - backgroundColor: AppColors.lightGrayBGColor, - chipText: item, - iconAsset: null, - isShowBorder: false, - borderRadius: 8.h, - textColor: AppColors.textColor) + height: 30.h, + chipType: ChipTypeEnum.alert, + backgroundColor: AppColors.lightGrayBGColor, + chipText: item, + iconAsset: null, + isShowBorder: false, + borderRadius: 8.h, + textColor: AppColors.textColor) .paddingOnly(bottom: 4.h); }).toList(), ), @@ -1117,8 +1027,8 @@ class _SelectClinicPageState extends State { // AppointmentViaRegionViewmodel? viewmodel = null; showCommonBottomSheetWithoutHeight(context, title: "", titleWidget: Consumer(builder: (_, data, __) => getTitle(data)), isDismissible: false, child: Consumer(builder: (context, data, __) { - return getRegionalSelectionWidget(data, context); - }), callBackFunc: () {}); + return getRegionalSelectionWidget(data, context); + }), callBackFunc: () {}); } Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data, BuildContext context) { diff --git a/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart b/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart index 3f86e380..42445fab 100644 --- a/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart +++ b/lib/presentation/health_calculators_and_converts/health_calculator_detailed_page.dart @@ -45,89 +45,110 @@ class _HealthCalculatorDetailedPageState extends State.from(result) : null); + calculatedResult = map != null + ? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID} + : null; + break; + case HealthCalculatorsTypeEnum.calories: + case HealthCalculatorsTypeEnum.bmr: + final map = provider.caloriesResultMap ?? (result is Map ? Map.from(result) : null); + calculatedResult = map != null + ? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID} + : null; + break; + case HealthCalculatorsTypeEnum.idealBodyWeight: + final map = provider.ibwResultMap ?? (result is Map ? Map.from(result) : null); + calculatedResult = map != null + ? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID} + : null; + break; + case HealthCalculatorsTypeEnum.bodyFat: + final map = provider.bodyFatResultMap ?? (result is Map ? Map.from(result) : null); + calculatedResult = map != null + ? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID} + : null; + break; + case HealthCalculatorsTypeEnum.crabsProteinFat: + final map = provider.macrosResultMap ?? (result is Map ? Map.from(result) : null); + calculatedResult = map != null + ? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID} + : null; + break; + case HealthCalculatorsTypeEnum.ovulation: + final map = (provider.ovulationResult is Map) + ? provider.ovulationResult + : (result is Map ? Map.from(result) : null); + calculatedResult = map != null + ? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID} + : null; + break; + case HealthCalculatorsTypeEnum.deliveryDueDate: + final map = (provider.deliveryResult is Map) + ? provider.deliveryResult + : (result is Map ? Map.from(result) : null); + calculatedResult = map != null + ? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID} + : null; + break; + case HealthCalculatorsTypeEnum.bloodSugar: + final map = (provider.bloodSugarResult is Map) + ? provider.bloodSugarResult + : (result is Map ? Map.from(result) : null); + calculatedResult = map != null + ? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID} + : null; + break; + case HealthCalculatorsTypeEnum.bloodCholesterol: + final map = (provider.bloodCholesterolResult is Map) + ? provider.bloodCholesterolResult + : (result is Map ? Map.from(result) : null); + calculatedResult = map != null + ? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID} + : null; + break; + case HealthCalculatorsTypeEnum.triglycerides: + final map = (provider.triglyceridesResult is Map) + ? provider.triglyceridesResult + : (result is Map ? Map.from(result) : null); + calculatedResult = map != null + ? {...map, 'clinicId': widget.clinicID, 'calculationID': widget.calculationID} + : null; + break; + } + }); }, ).paddingSymmetrical(20.w, 24.h), ); @@ -140,68 +161,68 @@ class _HealthCalculatorDetailedPageState extends State Date: Sun, 23 Aug 2026 15:54:28 +0300 Subject: [PATCH 03/15] feat: enhance appointment logic with user authentication checks and project ID retrieval --- .../book_appointment/select_clinic_page.dart | 19 +++++++++++++++++-- .../book_appointment/select_doctor_page.dart | 2 +- .../widgets/appointment_calendar.dart | 2 +- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart index 5f76ab31..df597d0c 100644 --- a/lib/presentation/book_appointment/select_clinic_page.dart +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -1058,7 +1058,8 @@ class _SelectClinicPageState extends State { regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION); var appointmentsViewModel = Provider.of(context, listen: false); if (appointmentsViewModel.selectedClinic.clinicID == 253) { - appointmentsViewModel.setProjectID(regionalViewModel.selectedHospital?.patientDoctorAppointmentList!.first.iD?.toString() ?? ""); + var id =getProjectId(regionalViewModel); + appointmentsViewModel.setProjectID(id); appointmentsViewModel.resetLaserData(); appointmentsViewModel.getLaserClinic(); regionalViewModel.handleLastStepForClinicForDentalAndLaser(appointmentsViewModel.selectedClinic.clinicID ?? -1); @@ -1067,8 +1068,9 @@ class _SelectClinicPageState extends State { } } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) { var appointmentsViewModel = Provider.of(context, listen: false); + var id =getProjectId(regionalViewModel); regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION); - appointmentsViewModel.setProjectID(regionalViewModel.selectedHospital?.hospitalList.first.iD?.toString() ?? ""); + appointmentsViewModel.setProjectID(id); regionalViewModel.handleLastStepForClinicForDentalAndLaser(appointmentsViewModel.selectedClinic.clinicID ?? -1); } }, @@ -1379,4 +1381,17 @@ class _SelectClinicPageState extends State { callBackFunc: () {}, ); } + + String getProjectId(AppointmentViaRegionViewmodel regionalViewModel) { + try { + if(regionalViewModel.selectedHospital?.patientDoctorAppointmentList?.isNotEmpty ?? false) { + return regionalViewModel.selectedHospital!.patientDoctorAppointmentList?.first.projectID?.toString() ?? ""; + } else if(regionalViewModel.selectedHospital?.hospitalList.isNotEmpty ?? false) { + return regionalViewModel.selectedHospital!.hospitalList.first.iD?.toString() ?? ""; + } + } on Exception catch (e) { + + } + return ""; + } } diff --git a/lib/presentation/book_appointment/select_doctor_page.dart b/lib/presentation/book_appointment/select_doctor_page.dart index 661c9a30..cb2555f3 100644 --- a/lib/presentation/book_appointment/select_doctor_page.dart +++ b/lib/presentation/book_appointment/select_doctor_page.dart @@ -53,7 +53,7 @@ class _SelectDoctorPageState extends State { bookAppointmentsViewModel.getLiveCareDoctorsList(); } else { if (bookAppointmentsViewModel.selectedClinic.clinicID == 17) { - if (appState.getAuthenticatedUser()!.age! > 12) { + if (appState.isAuthenticated && appState.getAuthenticatedUser()!.age! > 12) { bookAppointmentsViewModel.getDentalChiefComplaintDoctorsList(); } else { bookAppointmentsViewModel.getDoctorsList(isNearest: false); diff --git a/lib/presentation/book_appointment/widgets/appointment_calendar.dart b/lib/presentation/book_appointment/widgets/appointment_calendar.dart index a95989a6..8d96c12c 100644 --- a/lib/presentation/book_appointment/widgets/appointment_calendar.dart +++ b/lib/presentation/book_appointment/widgets/appointment_calendar.dart @@ -278,7 +278,7 @@ class _AppointmentCalendarState extends State { bookAppointmentsViewModel.setProjectID(bookAppointmentsViewModel.selectedDoctor.projectID.toString()); bookAppointmentsViewModel.setSelectedClinic(selectedClinic); bookAppointmentsViewModel.setIsChiefComplaintsListLoading(true); - if(appState.getAuthenticatedUser()!.age! > 12) { + if(appState.isAuthenticated && appState.getAuthenticatedUser()!.age! > 12) { Navigator.of(context).push( CustomPageRoute( page: DentalChiefComplaintsPage(), From 02e5b07681a9a45dc60c727054e78e3132b42839 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 23 Aug 2026 16:54:45 +0300 Subject: [PATCH 04/15] updates --- .../book_appointments_repo.dart | 5 +++-- .../book_appointments_view_model.dart | 2 +- lib/features/paytabs/paytabs_view_model.dart | 2 +- lib/main.dart | 17 ++++++++++------- .../appointments/appointment_payment_page.dart | 4 ++-- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/features/book_appointments/book_appointments_repo.dart b/lib/features/book_appointments/book_appointments_repo.dart index 1e0b8739..a705d84a 100644 --- a/lib/features/book_appointments/book_appointments_repo.dart +++ b/lib/features/book_appointments/book_appointments_repo.dart @@ -32,7 +32,7 @@ abstract class BookAppointmentsRepo { Future>> getDoctorProfile(int clinicID, int projectID, int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError}); - Future>> getDoctorRatingDetails(int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError}); + Future>> getDoctorRatingDetails(int doctorId, int projectID, {Function(dynamic)? onSuccess, Function(String)? onError}); Future>> getDoctorFreeSlots(int clinicID, int projectID, int doctorId, bool isBookingForLiveCare, {bool continueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError}); @@ -314,9 +314,10 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo { } @override - Future>> getDoctorRatingDetails(int doctorId, {Function(dynamic)? onSuccess, Function(String)? onError}) async { + Future>> getDoctorRatingDetails(int doctorId, int projectID, {Function(dynamic)? onSuccess, Function(String)? onError}) async { Map mapDevice = { "DoctorID": doctorId, + "ProjectID": projectID, "PatientID": 0, "License": true, "IsRegistered": true, diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index 4831901d..dbed528e 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -806,7 +806,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { doctorDetailsList.clear(); notifyListeners(); - final result = await bookAppointmentsRepo.getDoctorRatingDetails(selectedDoctor.doctorID ?? 0, onSuccess: onSuccess, onError: onError); + final result = await bookAppointmentsRepo.getDoctorRatingDetails(selectedDoctor.doctorID ?? 0, selectedDoctor.projectID ?? 0, onSuccess: onSuccess, onError: onError); result.fold( (failure) async { diff --git a/lib/features/paytabs/paytabs_view_model.dart b/lib/features/paytabs/paytabs_view_model.dart index b2563452..3e36233d 100644 --- a/lib/features/paytabs/paytabs_view_model.dart +++ b/lib/features/paytabs/paytabs_view_model.dart @@ -320,7 +320,7 @@ class PayTabsViewModel extends ChangeNotifier { payfortResponseInsertModel.cardNumber = paytabsTransactionResponseModel.paymentInfo!.paymentDescription; payfortResponseInsertModel.cardBin = ""; payfortResponseInsertModel.installmentsNumber = 0; - payfortResponseInsertModel.status = paytabsTransactionResponseModel.isSuccess! ? "success" : "failed"; + payfortResponseInsertModel.status = paytabsTransactionResponseModel.isSuccess! ? "14" : "00"; payfortResponseInsertModel.merchantRef = transactionID; payfortResponseInsertModel.isRefund = false; payfortResponseInsertModel.remmeberMe = true; diff --git a/lib/main.dart b/lib/main.dart index b22bbb6b..223103ae 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,8 +7,11 @@ import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/active_prescriptions/active_prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart'; @@ -76,12 +79,12 @@ Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { // flutter3_32 pub run easy_localization:generate -O ./lib/generated -f keys -o locale_keys.g.dart --source-dir ./assets/langs -// class MyHttpOverrides extends HttpOverrides { -// @override -// HttpClient createHttpClient(SecurityContext? context) { -// return super.createHttpClient(context)..badCertificateCallback = (X509Certificate cert, String host, int port) => true; -// } -// } +class MyHttpOverrides extends HttpOverrides { + @override + HttpClient createHttpClient(SecurityContext? context) { + return super.createHttpClient(context)..badCertificateCallback = (X509Certificate cert, String host, int port) => true; + } +} Future callAppStateInitializations() async { final String deviceTypeId = (Platform.isIOS @@ -130,7 +133,7 @@ Future callInitializations() async { await AppDependencies.addDependencies(); SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); - // HttpOverrides.global = MyHttpOverrides(); + HttpOverrides.global = MyHttpOverrides(); await callAppStateInitializations(); // Initialize Security Service early to catch threats before app logic runs diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 5c8bacf3..fb2c3043 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -649,9 +649,9 @@ class _AppointmentPaymentPageState extends State { doctorID: widget.patientAppointmentHistoryResponseModel.doctorID.toString(), transactionID: transID, amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(), - fileNumber: widget.patientAppointmentHistoryResponseModel.patientID, + fileNumber: widget.patientAppointmentHistoryResponseModel.patientID.toString(), serviceID: "2", - projectID: widget.patientAppointmentHistoryResponseModel.projectID, + projectID: widget.patientAppointmentHistoryResponseModel.projectID.toString(), isSchedule: widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false, onSuccess: (PaytabsTransactionResponseModel transactionData) async { tamaraOrderID = transactionData.cartID!; From 0772beed1b0f3f5bb5d1655627207bd9eb69542a Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 24 Aug 2026 16:15:01 +0300 Subject: [PATCH 05/15] PayTabs Tamara updates --- lib/core/paytabs_config.dart | 5 + lib/features/paytabs/paytabs_view_model.dart | 7 +- .../appointment_payment_page.dart | 129 +++++++---------- .../immediate_livecare_payment_page.dart | 81 +++++++++-- .../book_appointment/select_clinic_page.dart | 2 +- .../book_appointment/widgets/doctor_card.dart | 2 +- .../wallet_payment_confirm_page.dart | 10 +- .../ancillary_order_payment_page.dart | 132 +++--------------- lib/presentation/todo_section/todo_page.dart | 1 + lib/splashPage.dart | 5 + 10 files changed, 173 insertions(+), 201 deletions(-) diff --git a/lib/core/paytabs_config.dart b/lib/core/paytabs_config.dart index 4d90ee8f..b1ba0dd4 100644 --- a/lib/core/paytabs_config.dart +++ b/lib/core/paytabs_config.dart @@ -3,6 +3,11 @@ class PaymentSdkDefaultConfig { static const String profileID = "119730"; static const String serverKey = "SWJ9LD22MT-JLJZLBKTLK-WGWTLWGTWR"; static const String clientKey = "C7K2DQ-PDDN6B-69B2K7-P9GB9T"; + + // static const String profileID = "135577"; + // static const String serverKey = "SDJ9LD22LR-J9LRBKZDRD-ZRZKWDWBRR"; + // static const String clientKey = "CBK2DQ-PDPN6P-BR2K9D-VPBTB2"; + static const String defaultCurrency = "SAR"; static const double defaultAmount = 10.0; static const String defaultMerchantCountryCode = "SA"; diff --git a/lib/features/paytabs/paytabs_view_model.dart b/lib/features/paytabs/paytabs_view_model.dart index 3e36233d..1418cc55 100644 --- a/lib/features/paytabs/paytabs_view_model.dart +++ b/lib/features/paytabs/paytabs_view_model.dart @@ -57,7 +57,8 @@ class PayTabsViewModel extends ChangeNotifier { amount: amount.toDouble(), showBillingInfo: false, forceShippingInfo: false, - currencyCode: PaymentSdkDefaultConfig.defaultCurrency, + // currencyCode: PaymentSdkDefaultConfig.defaultCurrency, + currencyCode: getIt.get().getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", merchantCountryCode: PaymentSdkDefaultConfig.defaultMerchantCountryCode, billingDetails: BillingDetails( "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" ?? "HMG Patient", @@ -126,7 +127,8 @@ class PayTabsViewModel extends ChangeNotifier { amount: amount.toDouble(), showBillingInfo: false, forceShippingInfo: false, - currencyCode: PaymentSdkDefaultConfig.defaultCurrency, + // currencyCode: PaymentSdkDefaultConfig.defaultCurrency, + currencyCode: getIt.get().getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", merchantCountryCode: PaymentSdkDefaultConfig.defaultMerchantCountryCode, billingDetails: BillingDetails("${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" ?? "HMG Patient", "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", appState.getAuthenticatedUser()!.mobileNumber ?? "0000000000", "Riyadh", "SA", "Riyadh", "Riyadh", "12626"), @@ -398,7 +400,6 @@ class PayTabsViewModel extends ChangeNotifier { tamaraRequestModel.accessCode = getIt.get().getAuthenticatedUser()!.mobileNumber!; tamaraRequestModel.appointmentNo = appointmentNo; tamaraRequestModel.customerName = "${getIt.get().getAuthenticatedUser()!.firstName} ${getIt.get().getAuthenticatedUser()!.lastName}"; - ; tamaraRequestModel.fileNumber = fileNumber; tamaraRequestModel.patientOutSA = getIt.get().getAuthenticatedUser()!.outSa == 1 ? true : false; tamaraRequestModel.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken); diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index fb2c3043..6e9bb7b6 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -632,7 +632,6 @@ class _AppointmentPaymentPageState extends State { ).paddingSymmetrical(16.h, 16.h), ).paddingSymmetrical(24.h, 0.h).onPress(() { selectedPaymentMethod = "TAMARA"; - transID = Utils.getAppointmentTransID( widget.patientAppointmentHistoryResponseModel.projectID, widget.patientAppointmentHistoryResponseModel.clinicID, @@ -660,86 +659,64 @@ class _AppointmentPaymentPageState extends State { await payfortViewModel.updateTamaraRequestStatus(responseMessage: "success", status: "14", clientRequestID: transID, tamaraOrderID: tamaraOrderID); await payfortViewModel.markAppointmentAsTamaraPaid( projectID: widget.patientAppointmentHistoryResponseModel.projectID, appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo); - await myAppointmentsViewModel.createAdvancePayment( - paymentMethodName: selectedPaymentMethod, - projectID: widget.patientAppointmentHistoryResponseModel.projectID, - clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, - appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), - payedAmount: num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), - paymentReference: transactionData.transactionReference!, - patientID: appState.getAuthenticatedUser()!.patientId.toString(), - patientType: appState.getAuthenticatedUser()!.patientType!, - onSuccess: (value) async { - print(value); - await myAppointmentsViewModel.addAdvanceNumberRequest( - advanceNumber: "Tamara-Advance-0000", - paymentReference: transactionData.transactionReference!, - appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), - onSuccess: (value) async { - if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) { - //TODO: Implement LiveCare Check-In API Call - await myAppointmentsViewModel.insertLiveCareVIDARequest( - clientRequestID: transID, - patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, - onSuccess: (apiResponse) { - Future.delayed(Duration(milliseconds: 500), () { - myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); - myAppointmentsViewModel.initAppointmentsViewModel(); - myAppointmentsViewModel.getPatientAppointments(true, false); - LoaderBottomSheet.hideLoader(); + await myAppointmentsViewModel.addAdvanceNumberRequest( + advanceNumber: "Tamara-Advance-0000", + paymentReference: transactionData.transactionReference!, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + onSuccess: (value) async { + if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) { + //TODO: Implement LiveCare Check-In API Call + await myAppointmentsViewModel.insertLiveCareVIDARequest( + clientRequestID: transID, + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + onSuccess: (apiResponse) { + Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + LoaderBottomSheet.hideLoader(); - getIt.get().setIsWalletAmountToBeLoaded(true); - getIt.get().initHabibWalletProvider(); - getIt.get().getPatientBalanceAmount(); + getIt.get().setIsWalletAmountToBeLoaded(true); + getIt.get().initHabibWalletProvider(); + getIt.get().getPatientBalanceAmount(); - Navigator.pushAndRemoveUntil( - context, - CustomPageRoute( - page: LandingNavigation(), - ), - (r) => false); - }); - }, - onError: (error) {}); - } else { - await myAppointmentsViewModel.generateAppointmentQR( - clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, - projectID: widget.patientAppointmentHistoryResponseModel.projectID, - appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), - isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, - onSuccess: (apiResponse) { - Future.delayed(Duration(milliseconds: 500), () { - myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); - myAppointmentsViewModel.initAppointmentsViewModel(); - myAppointmentsViewModel.getPatientAppointments(true, false); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }); + }, + onError: (error) {}); + } else { + await myAppointmentsViewModel.generateAppointmentQR( + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, + onSuccess: (apiResponse) { + Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); - getIt.get().setIsWalletAmountToBeLoaded(true); - getIt.get().initHabibWalletProvider(); - getIt.get().getPatientBalanceAmount(); + getIt.get().setIsWalletAmountToBeLoaded(true); + getIt.get().initHabibWalletProvider(); + getIt.get().getPatientBalanceAmount(); - LoaderBottomSheet.hideLoader(); - Navigator.pushAndRemoveUntil( - context, - CustomPageRoute( - page: LandingNavigation(), - ), - (r) => false); - }); + LoaderBottomSheet.hideLoader(); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); }); - } - }); - }, - onError: (err) { - LoaderBottomSheet.hideLoader(); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err.toString()), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); - }, onError: (err) async { + }); + } + }); + }, onError: (err) async { await payfortViewModel.updateTamaraRequestStatus(responseMessage: "Failed", status: "00", clientRequestID: transID, tamaraOrderID: tamaraOrderID); showCommonBottomSheetWithoutHeight( context, diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart index 9ef3f8fb..2ab1e303 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:developer'; import 'dart:io'; +import 'package:clarity_flutter/clarity_flutter.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; @@ -60,6 +61,7 @@ class _ImmediateLiveCarePaymentPageState extends State false); + Navigator.of(context).push( + CustomPageRoute( + page: ImmediateLiveCarePendingRequestPage(), + ), + ); + } else { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Unknown error occurred..."), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }, + onError: (err) async {}, + ); + } else { + openPaymentURL("TAMARA"); + } }) : SizedBox.shrink(), ], @@ -524,17 +579,24 @@ class _ImmediateLiveCarePaymentPageState extends State { bookAppointmentsViewModel.setSelectedClinic(clinic); bookAppointmentsViewModel.setIsDoctorsListLoading(true); searchEditingController.text = ""; - if (clinic.isLiveCareClinicAndOnline ?? false) { + if (appState.isAuthenticated && (clinic.isLiveCareClinicAndOnline ?? false)) { Navigator.of(context).push( CustomPageRoute( page: SelectLivecareClinicPage( diff --git a/lib/presentation/book_appointment/widgets/doctor_card.dart b/lib/presentation/book_appointment/widgets/doctor_card.dart index 8a48465a..d2f431a9 100644 --- a/lib/presentation/book_appointment/widgets/doctor_card.dart +++ b/lib/presentation/book_appointment/widgets/doctor_card.dart @@ -86,7 +86,7 @@ class DoctorCard extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h, applyThemeColor: false), SizedBox(height: 2.h), - "${isLoading ? 4.78 : doctorsListResponseModel.decimalDoctorRate}".toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true), + "${isLoading ? 4.78 : doctorsListResponseModel.decimalDoctorRate ?? 5.0}".toText11(isBold: true, color: AppColors.textColor, isEnglishOnly: true), ], ), ).circle(100).toShimmer2(isShow: isLoading), diff --git a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart index ce260ca0..1ddaaec9 100644 --- a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart +++ b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'dart:io'; import 'package:amazon_payfort/amazon_payfort.dart'; +import 'package:clarity_flutter/clarity_flutter.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -54,6 +55,7 @@ class _WalletPaymentConfirmPageState extends State { @override void initState() { + Clarity.setCurrentScreenName('Habib Wallet Payment Page'); scheduleMicrotask(() { payfortViewModel.initPayfortViewModel(); }); @@ -509,9 +511,11 @@ class _WalletPaymentConfirmPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LocaleKeys.totalAmountToPay.tr(context: context).toText16(isBold: true), - Utils.getPaymentAmountWithSymbol( - NumberFormat.decimalPattern().format(habibWalletVM.walletRechargeAmount).toString().toText24(isBold: true, isEnglishOnly: true), AppColors.blackColor, 15.h, - isSaudiCurrency: true), + ClarityUnmask( + child: Utils.getPaymentAmountWithSymbol( + NumberFormat.decimalPattern().format(habibWalletVM.walletRechargeAmount).toString().toText24(isBold: true, isEnglishOnly: true), AppColors.blackColor, 15.h, + isSaudiCurrency: true), + ), ], ).paddingSymmetrical(24.h, 0.h), SizedBox(height: 12.h), diff --git a/lib/presentation/todo_section/ancillary_order_payment_page.dart b/lib/presentation/todo_section/ancillary_order_payment_page.dart index 36a2d7c5..e7553df5 100644 --- a/lib/presentation/todo_section/ancillary_order_payment_page.dart +++ b/lib/presentation/todo_section/ancillary_order_payment_page.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:developer'; import 'dart:io'; +import 'package:clarity_flutter/clarity_flutter.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; @@ -77,25 +78,10 @@ class _AncillaryOrderPaymentPageState extends State { @override void initState() { + Clarity.setCurrentScreenName('Ancillary Payment Page'); scheduleMicrotask(() { payfortViewModel.initPayfortViewModel(); payfortViewModel.setIsApplePayConfigurationLoading(false); - - myAppointmentsViewModel.getTamaraInstallmentsDetails().then((val) { - // if (myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel != null && myAppointmentsViewModel.patientAppointmentShareResponseModel != null) { - if (myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel != null && - myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel?.minLimit?.amount != null && - myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel?.maxLimit?.amount != null) { - if (widget.totalAmount >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && - widget.totalAmount <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) { - if (mounted) { - setState(() { - isShowTamara = true; - }); - } - } - } - }); }); super.initState(); } @@ -393,84 +379,6 @@ class _AncillaryOrderPaymentPageState extends State { } }), SizedBox(height: 16.h), - isShowTamara - ? Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: false, - ), - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Utils.buildImgWithAssets(icon: AppAssets.tamaraEng, width: 72.h, height: 25.h).toShimmer2(isShow: false), - SizedBox(height: 16.h), - LocaleKeys.tamara.tr(context: context).toText16(isBold: true).toShimmer2(isShow: false), - ], - ), - SizedBox(width: 8.h), - const Spacer(), - Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon_small, - iconColor: AppColors.blackColor, - width: 18.h, - height: 13.h, - fit: BoxFit.contain, - ).toShimmer2(isShow: false), - ), - ], - ).paddingSymmetrical(16.h, 16.h), - ).paddingSymmetrical(24.h, 0.h).onPress(() { - selectedPaymentMethod = "TAMARA"; - transID = Utils.getAppointmentTransID( - widget.projectID, - widget.clinicID, - widget.appointmentNoVida, - ); - if (appState.isPaytabsEnabled) { - paytabsViewModel.setAlternatePaymentConfiguration("Ancillary Order Payment", widget.totalAmount); - paytabsViewModel.startAlternatePayment( - liveServiceID: "0", - appointmentDate: DateUtil.convertDateToString(widget.appointmentDate!), - appointmentNo: widget.appointmentNoVida.toString(), - doctorID: "0", - transactionID: transID, - amount: widget.totalAmount.toString(), - fileNumber: appState.getAuthenticatedUser()!.patientId.toString(), - serviceID: "2", - projectID: widget.projectID.toString(), - isSchedule: false, - onSuccess: (PaytabsTransactionResponseModel transactionData) async { - tamaraOrderID = transactionData.cartID!; - // transID = transactionData.transactionReference!; - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); - await payfortViewModel.updateTamaraRequestStatus(responseMessage: "success", status: "14", clientRequestID: transID, tamaraOrderID: tamaraOrderID); - _createAdvancePayment( - paymentAmount: widget.totalAmount, - paymentReference: transactionData.transactionReference!, - paymentMethod: selectedPaymentMethod, - ); - }, - onError: (err) async { - await payfortViewModel.updateTamaraRequestStatus(responseMessage: "Failed", status: "00", clientRequestID: transID, tamaraOrderID: tamaraOrderID); - showCommonBottomSheetWithoutHeight( - context, - child: Utils.getErrorWidget(loadingText: err.toString()), - callBackFunc: () {}, - isFullScreen: false, - isCloseButtonVisible: true, - ); - }); - } else { - _openPaymentURL("tamara"); - } - }) - : SizedBox.shrink(), ], ), ), @@ -514,11 +422,13 @@ class _AncillaryOrderPaymentPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), - Utils.getPaymentAmountWithSymbol( - amountBeforeTax.toStringAsFixed(2).toText16(isBold: true), - AppColors.blackColor, - 13, - isSaudiCurrency: true, + ClarityUnmask( + child: Utils.getPaymentAmountWithSymbol( + amountBeforeTax.toStringAsFixed(2).toText16(isBold: true), + AppColors.blackColor, + 13, + isSaudiCurrency: true, + ), ), ], ).paddingSymmetrical(24.h, 0.h), @@ -528,11 +438,13 @@ class _AncillaryOrderPaymentPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor), - Utils.getPaymentAmountWithSymbol( - taxAmount.toStringAsFixed(2).toText14(isBold: true, color: AppColors.greyTextColor), - AppColors.greyTextColor, - 13, - isSaudiCurrency: true, + ClarityUnmask( + child: Utils.getPaymentAmountWithSymbol( + taxAmount.toStringAsFixed(2).toText14(isBold: true, color: AppColors.greyTextColor), + AppColors.greyTextColor, + 13, + isSaudiCurrency: true, + ), ), ], ).paddingSymmetrical(24.h, 0.h), @@ -544,11 +456,13 @@ class _AncillaryOrderPaymentPageState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ "".toText14(isBold: true), - Utils.getPaymentAmountWithSymbol( - widget.totalAmount.toStringAsFixed(2).toText24(isBold: true), - AppColors.blackColor, - 17, - isSaudiCurrency: true, + ClarityUnmask( + child: Utils.getPaymentAmountWithSymbol( + widget.totalAmount.toStringAsFixed(2).toText24(isBold: true), + AppColors.blackColor, + 17, + isSaudiCurrency: true, + ), ), ], ).paddingSymmetrical(24.h, 0.h), diff --git a/lib/presentation/todo_section/todo_page.dart b/lib/presentation/todo_section/todo_page.dart index 2f1061d2..7f59b2d2 100644 --- a/lib/presentation/todo_section/todo_page.dart +++ b/lib/presentation/todo_section/todo_page.dart @@ -34,6 +34,7 @@ class _ToDoPageState extends State { scheduleMicrotask(() async { if (appState.isAuthenticated) { await todoSectionViewModel.initializeTodoSectionViewModel(); + todoSectionViewModel.setProcessingPayment(false); } }); super.initState(); diff --git a/lib/splashPage.dart b/lib/splashPage.dart index 8b39bf28..fe223fc2 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -13,9 +13,11 @@ 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/enums.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; +import 'package:hmg_patient_app_new/main.dart'; import 'package:hmg_patient_app_new/presentation/home/app_update_page.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/onboarding/onboarding_screen.dart'; @@ -58,6 +60,9 @@ class _SplashScreenState extends State { () async { ApiConsts.setBackendURLs(); PushNotificationHandler().init(context); // Asyncronously + // if (ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat) { + // HttpOverrides.global = MyHttpOverrides(); + // } }, ); From 112c254416ff91cf6d26ce9d9cf49c829ba7697e Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 25 Aug 2026 10:31:32 +0300 Subject: [PATCH 06/15] Update to stores VersionID 21.9 --- lib/core/api_consts.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 7d3b97d4..094fa680 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -5,7 +5,7 @@ import 'package:hmg_patient_app_new/core/enums.dart'; class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/pubspec.yaml b/pubspec.yaml index 0c5d78dd..e471cada 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: "New HMG Patient App" publish_to: 'none' # Remove this line if you wish to publish to pub.dev #version: 0.0.45+46 -version: 0.0.14+3 +version: 0.0.15+2 environment: sdk: ">=3.6.0 <4.0.0" From f6459142e187b50c40a3cf9ae5974559a717129e Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 25 Aug 2026 17:10:42 +0300 Subject: [PATCH 07/15] Paytabs dynamic keys implemented --- .../immediate_livecare_repo.dart | 2 - lib/features/payfort/payfort_repo.dart | 2 +- lib/features/paytabs/paytabs_view_model.dart | 54 +++++++++++++++---- .../appointment_payment_page.dart | 44 ++++++++------- .../immediate_livecare_payment_page.dart | 34 ++++++------ .../waiting_appointment_payment_page.dart | 6 +-- .../er_online_checkin_payment_page.dart | 6 +-- .../wallet_payment_confirm_page.dart | 13 +++-- .../ancillary_order_payment_page.dart | 30 +++++------ 9 files changed, 111 insertions(+), 80 deletions(-) diff --git a/lib/features/immediate_livecare/immediate_livecare_repo.dart b/lib/features/immediate_livecare/immediate_livecare_repo.dart index 6726a09b..e07d6293 100644 --- a/lib/features/immediate_livecare/immediate_livecare_repo.dart +++ b/lib/features/immediate_livecare/immediate_livecare_repo.dart @@ -101,9 +101,7 @@ class ImmediateLiveCareRepoImp implements ImmediateLiveCareRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { final respObject = response['GetERAppointmentFeesList']; - final liveCareFeesObj = LiveCareImmediateAppointmentFeesList.fromJson(respObject); - apiResponse = GenericApiModel( messageStatus: messageStatus, statusCode: statusCode, diff --git a/lib/features/payfort/payfort_repo.dart b/lib/features/payfort/payfort_repo.dart index 9262d662..6826478c 100644 --- a/lib/features/payfort/payfort_repo.dart +++ b/lib/features/payfort/payfort_repo.dart @@ -58,7 +58,7 @@ class PayfortRepoImp implements PayfortRepo { try { final list = response; if (list == null || list.isEmpty) { - throw Exception("payfort list is empty"); + throw Exception("No project configuration found."); } final applePayConfigurationResponse = PayfortProjectDetailsRespModel.fromJson(list[0]); diff --git a/lib/features/paytabs/paytabs_view_model.dart b/lib/features/paytabs/paytabs_view_model.dart index 1418cc55..a7894f07 100644 --- a/lib/features/paytabs/paytabs_view_model.dart +++ b/lib/features/paytabs/paytabs_view_model.dart @@ -17,6 +17,7 @@ import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/paytabs_config.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/payfort/models/payfort_project_details_resp_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/payfort_request_insert_model.dart'; @@ -32,19 +33,26 @@ class PayTabsViewModel extends ChangeNotifier { final ErrorHandlerService errorHandlerService; late PaytabsTransactionResponseModel paytabsTransactionResponseModel; + PayfortProjectDetailsRespModel? paytabsProjectDetailsRespModel; + PayTabsViewModel({ required this.appState, required this.payfortRepo, required this.errorHandlerService, }); - setPaymentConfiguration(String paymentDescription, num amount, {bool isAddCard = false}) { + setPaymentConfiguration(String paymentDescription, num amount, int serviceID, int projectID, {bool isAddCard = false}) async { String cartID = DateTime.now().millisecondsSinceEpoch.toString(); + + LoaderBottomSheet.showLoader(); + + await getPayTabsConfigurations(serviceId: serviceID, projectId: projectID); + paymentConfiguration = PaymentSdkConfigurationDetails( - profileId: PaymentSdkDefaultConfig.profileID, - serverKey: PaymentSdkDefaultConfig.serverKey, - clientKey: PaymentSdkDefaultConfig.clientKey, - transactionType: PaymentSdkTransactionType.SALE, + profileId: paytabsProjectDetailsRespModel!.merchantIdentifier, + serverKey: paytabsProjectDetailsRespModel!.accessCode, + clientKey: paytabsProjectDetailsRespModel!.shaResponse, + transactionType: PaymentSdkTransactionType.SALE, cartId: cartID.substring((cartID.length - 5), cartID.length), cartDescription: paymentDescription, merchantName: PaymentSdkDefaultConfig.defaultMerchantName, @@ -112,12 +120,17 @@ class PayTabsViewModel extends ChangeNotifier { notifyListeners(); } - setAlternatePaymentConfiguration(String paymentDescription, num amount, {bool isAddCard = false}) { + setAlternatePaymentConfiguration(String paymentDescription, num amount, int serviceID, int projectID, {bool isAddCard = false}) async { String cartID = DateTime.now().millisecondsSinceEpoch.toString(); + + LoaderBottomSheet.showLoader(); + + await getPayTabsConfigurations(serviceId: serviceID, projectId: projectID); + paymentConfiguration = PaymentSdkConfigurationDetails( - profileId: PaymentSdkDefaultConfig.profileID, - serverKey: PaymentSdkDefaultConfig.serverKey, - clientKey: PaymentSdkDefaultConfig.clientKey, + profileId: paytabsProjectDetailsRespModel!.merchantIdentifier, + serverKey: paytabsProjectDetailsRespModel!.accessCode, + clientKey: paytabsProjectDetailsRespModel!.shaResponse, transactionType: PaymentSdkTransactionType.SALE, cartId: cartID.substring((cartID.length - 5), cartID.length), cartDescription: paymentDescription, @@ -260,7 +273,7 @@ class PayTabsViewModel extends ChangeNotifier { bool _callbackFired = false; // prevent duplicate callback invocations try { - LoaderBottomSheet.showLoader(); + // LoaderBottomSheet.showLoader(); // Create PayfortRequestInsertModel (same as Payfort implementation) PayfortRequestInsertModel payfortRequestInsertModel = PayfortRequestInsertModel(); @@ -382,7 +395,7 @@ class PayTabsViewModel extends ChangeNotifier { required bool isSchedule, Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) async { - LoaderBottomSheet.showLoader(); + // LoaderBottomSheet.showLoader(); TamaraRequestModel tamaraRequestModel = TamaraRequestModel(); @@ -448,4 +461,23 @@ class PayTabsViewModel extends ChangeNotifier { } }); } + + Future getPayTabsConfigurations({int? serviceId, int? projectId, int integrationId = 5, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await payfortRepo.getPayfortConfigurations(serviceId: serviceId, projectId: projectId, integrationId: integrationId); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + paytabsProjectDetailsRespModel = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 6e9bb7b6..bf2c0fcd 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -150,10 +150,11 @@ class _AppointmentPaymentPageState extends State { widget.patientAppointmentHistoryResponseModel.clinicID, widget.patientAppointmentHistoryResponseModel.appointmentNo, ); - paytabsViewModel.setPaymentConfiguration( - "Appointment Payment", - num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), - ); + await paytabsViewModel.setPaymentConfiguration( + "Appointment Payment", + num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), + ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), + widget.patientAppointmentHistoryResponseModel.projectID); paytabsViewModel.startCardPayment( paymentOption: selectedPaymentMethod, liveServiceID: "0", @@ -179,7 +180,6 @@ class _AppointmentPaymentPageState extends State { patientID: appState.getAuthenticatedUser()!.patientId.toString(), patientType: appState.getAuthenticatedUser()!.patientType!, onSuccess: (value) async { - print(value); await myAppointmentsViewModel.addAdvanceNumberRequest( advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID) ? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString() @@ -263,7 +263,6 @@ class _AppointmentPaymentPageState extends State { orderDescription: "Appointment Payment", amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(), onSucceeded: (paymentResult) async { - print("Payment Success Data: ${paymentResult.toString()}"); LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); await myAppointmentsViewModel.createAdvancePayment( paymentMethodName: selectedPaymentMethod, @@ -275,7 +274,6 @@ class _AppointmentPaymentPageState extends State { patientID: appState.getAuthenticatedUser()!.patientId.toString(), patientType: appState.getAuthenticatedUser()!.patientType!, onSuccess: (value) async { - print(value); await myAppointmentsViewModel.addAdvanceNumberRequest( advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID) ? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString() @@ -391,10 +389,11 @@ class _AppointmentPaymentPageState extends State { widget.patientAppointmentHistoryResponseModel.clinicID, widget.patientAppointmentHistoryResponseModel.appointmentNo, ); - paytabsViewModel.setPaymentConfiguration( - "Appointment Payment", - num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), - ); + await paytabsViewModel.setPaymentConfiguration( + "Appointment Payment", + num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), + ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), + widget.patientAppointmentHistoryResponseModel.projectID); paytabsViewModel.startCardPayment( paymentOption: selectedPaymentMethod, liveServiceID: "0", @@ -420,7 +419,6 @@ class _AppointmentPaymentPageState extends State { patientID: appState.getAuthenticatedUser()!.patientId.toString(), patientType: appState.getAuthenticatedUser()!.patientType!, onSuccess: (value) async { - print(value); await myAppointmentsViewModel.addAdvanceNumberRequest( advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID) ? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString() @@ -504,7 +502,6 @@ class _AppointmentPaymentPageState extends State { amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(), orderDescription: "Appointment Payment", onSucceeded: (paymentResult) async { - print("Payment Success Data: ${paymentResult.toString()}"); LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); await myAppointmentsViewModel.createAdvancePayment( paymentMethodName: selectedPaymentMethod, @@ -516,7 +513,6 @@ class _AppointmentPaymentPageState extends State { patientID: appState.getAuthenticatedUser()!.patientId.toString(), patientType: appState.getAuthenticatedUser()!.patientType!, onSuccess: (value) async { - print(value); await myAppointmentsViewModel.addAdvanceNumberRequest( advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID) ? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString() @@ -640,7 +636,10 @@ class _AppointmentPaymentPageState extends State { if (appState.isPaytabsEnabled) { paytabsViewModel.setAlternatePaymentConfiguration( - "Appointment Payment", num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString())); + "Appointment Payment", + num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), + ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), + widget.patientAppointmentHistoryResponseModel.projectID); paytabsViewModel.startAlternatePayment( liveServiceID: "0", appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate!, @@ -1121,10 +1120,13 @@ class _AppointmentPaymentPageState extends State { ApplePayInsertRequest applePayInsertRequest = ApplePayInsertRequest(); - await payfortViewModel.getPayfortConfigurations( - serviceId: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), - projectId: widget.patientAppointmentHistoryResponseModel.projectID, - integrationId: 2); + if (!appState.isPaytabsEnabled) { + await payfortViewModel.getPayfortConfigurations( + serviceId: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), projectId: widget.patientAppointmentHistoryResponseModel.projectID, integrationId: 2); + } else { + await paytabsViewModel.setPaymentConfiguration("Appointment Payment", num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), + ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), widget.patientAppointmentHistoryResponseModel.projectID); + } applePayInsertRequest.clientRequestID = transID; applePayInsertRequest.clinicID = widget.patientAppointmentHistoryResponseModel.clinicID; @@ -1173,10 +1175,6 @@ class _AppointmentPaymentPageState extends State { if (appState.isPaytabsEnabled) { selectedPaymentMethod = "VISA"; LoaderBottomSheet.hideLoader(); - paytabsViewModel.setPaymentConfiguration( - "Appointment Payment", - num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), - ); paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); await myAppointmentsViewModel.createAdvancePayment( diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart index 2ab1e303..c51ac120 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart @@ -137,10 +137,8 @@ class _ImmediateLiveCarePaymentPageState extends State paytabsViewModel.setPaymentConfiguration( "ER Online Check-In Payment", emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!, - ); + ServiceTypeEnum.erOnlineCheckIn.getIdFromServiceEnum(), emergencyServicesViewModel.selectedHospital!.iD); paytabsViewModel.startCardPayment( paymentOption: selectedPaymentMethod, liveServiceID: "0", @@ -238,7 +238,7 @@ class _ErOnlineCheckinPaymentPageState extends State paytabsViewModel.setPaymentConfiguration( "ER Online Check-In Payment", emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!, - ); + ServiceTypeEnum.erOnlineCheckIn.getIdFromServiceEnum(), emergencyServicesViewModel.selectedHospital!.iD); paytabsViewModel.startCardPayment( paymentOption: selectedPaymentMethod, liveServiceID: "0", @@ -533,7 +533,7 @@ class _ErOnlineCheckinPaymentPageState extends State paytabsViewModel.setPaymentConfiguration( "ER Online Check-In Payment", emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!, - ); + ServiceTypeEnum.erOnlineCheckIn.getIdFromServiceEnum(), emergencyServicesViewModel.selectedHospital!.iD); paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); if (emergencyServicesViewModel.isERBookAppointment) { diff --git a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart index 1ddaaec9..c407e6ad 100644 --- a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart +++ b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart @@ -116,7 +116,8 @@ class _WalletPaymentConfirmPageState extends State { selectedPaymentMethod = "MADA"; if (appState.isPaytabsEnabled) { transID = Utils.getAdvancePaymentTransID(habibWalletVM.selectedHospital!.iD!, int.parse(habibWalletVM.fileNumber)); - paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount); + await paytabsViewModel.setPaymentConfiguration( + "Advance Payment", habibWalletVM.walletRechargeAmount, ServiceTypeEnum.advancePayment.getIdFromServiceEnum(), habibWalletVM.selectedHospital!.iD!); paytabsViewModel.startCardPayment( paymentOption: selectedPaymentMethod, liveServiceID: "0", @@ -313,7 +314,8 @@ class _WalletPaymentConfirmPageState extends State { selectedPaymentMethod = "VISA"; if (appState.isPaytabsEnabled) { transID = Utils.getAdvancePaymentTransID(habibWalletVM.selectedHospital!.iD!, int.parse(habibWalletVM.fileNumber)); - paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount); + await paytabsViewModel.setPaymentConfiguration( + "Advance Payment", habibWalletVM.walletRechargeAmount, ServiceTypeEnum.advancePayment.getIdFromServiceEnum(), habibWalletVM.selectedHospital!.iD!); paytabsViewModel.startCardPayment( paymentOption: selectedPaymentMethod, liveServiceID: "0", @@ -543,7 +545,11 @@ class _WalletPaymentConfirmPageState extends State { transID = Utils.getAdvancePaymentTransID(habibWalletVM.selectedHospital!.iD!, int.parse(habibWalletVM.fileNumber)); - await payfortViewModel.getPayfortConfigurations(serviceId: ServiceTypeEnum.advancePayment.getIdFromServiceEnum(), projectId: habibWalletVM.selectedHospital!.iD!, integrationId: 2); + if(!appState.isPaytabsEnabled) { + await payfortViewModel.getPayfortConfigurations(serviceId: ServiceTypeEnum.advancePayment.getIdFromServiceEnum(), projectId: habibWalletVM.selectedHospital!.iD!, integrationId: 2); + } else { + await paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount, ServiceTypeEnum.advancePayment.getIdFromServiceEnum(), habibWalletVM.selectedHospital!.iD!); + } applePayInsertRequest.clientRequestID = transID; applePayInsertRequest.clinicID = 0; @@ -592,7 +598,6 @@ class _WalletPaymentConfirmPageState extends State { if(appState.isPaytabsEnabled) { selectedPaymentMethod = "VISA"; LoaderBottomSheet.hideLoader(); - paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount); paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { LoaderBottomSheet.showLoader(); await habibWalletVM.HISCreateAdvancePayment( diff --git a/lib/presentation/todo_section/ancillary_order_payment_page.dart b/lib/presentation/todo_section/ancillary_order_payment_page.dart index e7553df5..365dff2b 100644 --- a/lib/presentation/todo_section/ancillary_order_payment_page.dart +++ b/lib/presentation/todo_section/ancillary_order_payment_page.dart @@ -150,10 +150,7 @@ class _AncillaryOrderPaymentPageState extends State { widget.projectID, appState.getAuthenticatedUser()!.patientId!, ); - paytabsViewModel.setPaymentConfiguration( - "Ancillary Orders Payment", - widget.totalAmount, - ); + await paytabsViewModel.setPaymentConfiguration("Ancillary Orders Payment", widget.totalAmount, ServiceTypeEnum.advancePayment.getIdFromServiceEnum(), widget.projectID); paytabsViewModel.startCardPayment( paymentOption: selectedPaymentMethod, liveServiceID: "0", @@ -289,10 +286,7 @@ class _AncillaryOrderPaymentPageState extends State { widget.projectID, appState.getAuthenticatedUser()!.patientId!, ); - paytabsViewModel.setPaymentConfiguration( - "Ancillary Orders Payment", - widget.totalAmount, - ); + await paytabsViewModel.setPaymentConfiguration("Ancillary Orders Payment", widget.totalAmount, ServiceTypeEnum.advancePayment.getIdFromServiceEnum(), widget.projectID); paytabsViewModel.startCardPayment(paymentOption: selectedPaymentMethod, liveServiceID: "0", isScheduleLiveCare: "false", @@ -721,11 +715,13 @@ class _AncillaryOrderPaymentPageState extends State { transID = Utils.getAdvancePaymentTransID(widget.projectID, user!.patientId!); ApplePayInsertRequest applePayInsertRequest = ApplePayInsertRequest(); - await payfortViewModel.getPayfortConfigurations( - serviceId: ServiceTypeEnum.ancillaryOrder.getIdFromServiceEnum(), - projectId: widget.projectID, - integrationId: 2, - ); + if (!appState.isPaytabsEnabled) { + await payfortViewModel.getPayfortConfigurations( + serviceId: ServiceTypeEnum.ancillaryOrder.getIdFromServiceEnum(), + projectId: widget.projectID, + integrationId: 2, + ); + } applePayInsertRequest.clientRequestID = transID; applePayInsertRequest.clinicID = 0; @@ -770,6 +766,10 @@ class _AncillaryOrderPaymentPageState extends State { applePayInsertRequest.returnURL = ""; try { + if (appState.isPaytabsEnabled) { + await paytabsViewModel.setPaymentConfiguration("Ancillary Orders Payment", widget.totalAmount, ServiceTypeEnum.advancePayment.getIdFromServiceEnum(), widget.projectID); + } + await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest); } catch (error) { log("Apple Pay Insert Request Failed: $error"); @@ -787,10 +787,6 @@ class _AncillaryOrderPaymentPageState extends State { if(appState.isPaytabsEnabled) { selectedPaymentMethod = "VISA"; LoaderBottomSheet.hideLoader(); - paytabsViewModel.setPaymentConfiguration( - "Ancillary Orders Payment", - widget.totalAmount, - ); paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { final paymentAmount = widget.totalAmount; final fortId = transactionData.transactionReference; From e82ba228d515a884f1fd13a12405a54b4c6e5d74 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 26 Aug 2026 11:14:34 +0300 Subject: [PATCH 08/15] PayTabs implementation contd. --- .../appointments/appointment_payment_page.dart | 11 ++++++----- .../immediate_livecare_payment_page.dart | 17 ++++++++--------- .../wallet_payment_confirm_page.dart | 10 +++++----- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index bf2c0fcd..85a606b3 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -1162,12 +1162,13 @@ class _AppointmentPaymentPageState extends State { applePayInsertRequest.isMobSDK = true; applePayInsertRequest.merchantReference = transID; - applePayInsertRequest.merchantIdentifier = payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier; + applePayInsertRequest.merchantIdentifier = + appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.merchantIdentifier : payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier; applePayInsertRequest.commandType = "PURCHASE"; - applePayInsertRequest.signature = payfortViewModel.payfortProjectDetailsRespModel!.signature; - applePayInsertRequest.accessCode = payfortViewModel.payfortProjectDetailsRespModel!.accessCode; - applePayInsertRequest.shaRequestPhrase = payfortViewModel.payfortProjectDetailsRespModel!.shaRequest; - applePayInsertRequest.shaResponsePhrase = payfortViewModel.payfortProjectDetailsRespModel!.shaResponse; + applePayInsertRequest.signature = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.signature : payfortViewModel.payfortProjectDetailsRespModel!.signature; + applePayInsertRequest.accessCode = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.accessCode : payfortViewModel.payfortProjectDetailsRespModel!.accessCode; + applePayInsertRequest.shaRequestPhrase = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.shaRequest : payfortViewModel.payfortProjectDetailsRespModel!.shaRequest; + applePayInsertRequest.shaResponsePhrase = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.shaResponse : payfortViewModel.payfortProjectDetailsRespModel!.shaResponse; applePayInsertRequest.returnURL = ""; //TODO: Need to pass dynamic params to the Apple Pay instead of static values diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart index c51ac120..692e8b44 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart @@ -147,7 +147,7 @@ class _ImmediateLiveCarePaymentPageState extends State { applePayInsertRequest.isMobSDK = true; applePayInsertRequest.merchantReference = transID; - applePayInsertRequest.merchantIdentifier = payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier; + applePayInsertRequest.merchantIdentifier = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.merchantIdentifier : payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier; applePayInsertRequest.commandType = "PURCHASE"; - applePayInsertRequest.signature = payfortViewModel.payfortProjectDetailsRespModel!.signature; - applePayInsertRequest.accessCode = payfortViewModel.payfortProjectDetailsRespModel!.accessCode; - applePayInsertRequest.shaRequestPhrase = payfortViewModel.payfortProjectDetailsRespModel!.shaRequest; - applePayInsertRequest.shaResponsePhrase = payfortViewModel.payfortProjectDetailsRespModel!.shaResponse; + applePayInsertRequest.signature = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.signature : payfortViewModel.payfortProjectDetailsRespModel!.signature; + applePayInsertRequest.accessCode = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.accessCode : payfortViewModel.payfortProjectDetailsRespModel!.accessCode; + applePayInsertRequest.shaRequestPhrase = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.shaRequest : payfortViewModel.payfortProjectDetailsRespModel!.shaRequest; + applePayInsertRequest.shaResponsePhrase = appState.isPaytabsEnabled ? paytabsViewModel.paytabsProjectDetailsRespModel!.shaResponse : payfortViewModel.payfortProjectDetailsRespModel!.shaResponse; applePayInsertRequest.returnURL = ""; //TODO: Need to pass dynamic params to the Apple Pay instead of static values From 26c20e110378d7d26c6a6d90cf33d6f344a472df Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 30 Aug 2026 10:06:26 +0300 Subject: [PATCH 09/15] appointment checkin privilege applied --- .../widgets/appointment_checkin_bottom_sheet.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart index dd6cf8eb..acf2a274 100644 --- a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart +++ b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart @@ -45,8 +45,9 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - checkInOptionCard( - AppAssets.checkin_location_icon, + Utils.havePrivilege(102) + ? checkInOptionCard( + AppAssets.checkin_location_icon, LocaleKeys.liveLocation.tr(context: context), LocaleKeys.verifyYourLocationAtHospital.tr(context: context), ).onPress(() { @@ -71,9 +72,10 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { }, isFullScreen: false); } }); - }), + }) + : SizedBox.shrink(), // SizedBox(height: 16.h), - // checkInOptionCard(O + // checkInOptionCard( // AppAssets.checkin_nfc_icon, // LocaleKeys.nfcNearFieldCommunication.tr(context: context), // LocaleKeys.scanPhoneViaNFC.tr(context: context), From 81bf111c2e47fd3297137a1751d6c8ca67b17b5e Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 30 Aug 2026 10:29:51 +0300 Subject: [PATCH 10/15] updates --- .../widgets/appointment_doctor_card.dart | 109 ++++++++++++------ lib/presentation/home/landing_page.dart | 31 +++-- 2 files changed, 90 insertions(+), 50 deletions(-) diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index 8738a449..dc7bb0af 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -173,39 +173,39 @@ class AppointmentDoctorCard extends StatelessWidget { ? SizedBox.shrink() : Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown) .onPress(() async { - DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel(); - selectedDoctor.doctorID = patientAppointmentHistoryResponseModel.doctorID; - selectedDoctor.doctorImageURL = patientAppointmentHistoryResponseModel.doctorImageURL; - selectedDoctor.name = patientAppointmentHistoryResponseModel.doctorNameObj; - selectedDoctor.doctorTitle = patientAppointmentHistoryResponseModel.doctorTitle; - selectedDoctor.nationalityFlagURL = ""; - selectedDoctor.speciality = patientAppointmentHistoryResponseModel.doctorSpeciality; - selectedDoctor.clinicName = patientAppointmentHistoryResponseModel.clinicName; - selectedDoctor.projectName = patientAppointmentHistoryResponseModel.projectName; - selectedDoctor.clinicID = patientAppointmentHistoryResponseModel.clinicID; - selectedDoctor.projectID = patientAppointmentHistoryResponseModel.projectID; - - getIt.get().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, - ); - }); + // DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel(); + // selectedDoctor.doctorID = patientAppointmentHistoryResponseModel.doctorID; + // selectedDoctor.doctorImageURL = patientAppointmentHistoryResponseModel.doctorImageURL; + // selectedDoctor.name = patientAppointmentHistoryResponseModel.doctorNameObj; + // selectedDoctor.doctorTitle = patientAppointmentHistoryResponseModel.doctorTitle; + // selectedDoctor.nationalityFlagURL = ""; + // selectedDoctor.speciality = patientAppointmentHistoryResponseModel.doctorSpeciality; + // selectedDoctor.clinicName = patientAppointmentHistoryResponseModel.clinicName; + // selectedDoctor.projectName = patientAppointmentHistoryResponseModel.projectName; + // selectedDoctor.clinicID = patientAppointmentHistoryResponseModel.clinicID; + // selectedDoctor.projectID = patientAppointmentHistoryResponseModel.projectID; + // + // getIt.get().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, + // ); + // }); }) ], ), @@ -220,7 +220,48 @@ class AppointmentDoctorCard extends StatelessWidget { ], ), ), - ); + ).onPress(() async { + if ((patientAppointmentHistoryResponseModel.isLiveCareAppointment! || + patientAppointmentHistoryResponseModel.isExecludeDoctor! || + patientAppointmentHistoryResponseModel.isClinicReBookingAllowed! == false || + patientAppointmentHistoryResponseModel.isActiveDoctor! == false || + !Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID))) { + } else { + DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel(); + selectedDoctor.doctorID = patientAppointmentHistoryResponseModel.doctorID; + selectedDoctor.doctorImageURL = patientAppointmentHistoryResponseModel.doctorImageURL; + selectedDoctor.name = patientAppointmentHistoryResponseModel.doctorNameObj; + selectedDoctor.doctorTitle = patientAppointmentHistoryResponseModel.doctorTitle; + selectedDoctor.nationalityFlagURL = ""; + selectedDoctor.speciality = patientAppointmentHistoryResponseModel.doctorSpeciality; + selectedDoctor.clinicName = patientAppointmentHistoryResponseModel.clinicName; + selectedDoctor.projectName = patientAppointmentHistoryResponseModel.projectName; + selectedDoctor.clinicID = patientAppointmentHistoryResponseModel.clinicID; + selectedDoctor.projectID = patientAppointmentHistoryResponseModel.projectID; + + getIt.get().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, + ); + }); + } + }); } Widget getAppointmentActionButtons(bool isAppointmentArrived) { diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index bf1a7b62..1f8def35 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -357,21 +357,7 @@ class _LandingPageState extends State { // spacing: 18.h, children: [ Stack(clipBehavior: Clip.none, children: [ - if (appState.isAuthenticated) - Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 24.h, width: 24.h).onPress(() async { - if (appState.isAuthenticated) { - notificationsViewModel.setNotificationStatusID(2); - notificationsViewModel.getAllNotifications(); - Navigator.of(context).push( - CustomPageRoute( - page: NotificationsListPage(), - // page: LoginScreen(), - ), - ); - } else { - await authVM.onLoginPressed(); - } - }), + if (appState.isAuthenticated) Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 24.h, width: 24.h), (appState.isAuthenticated && todoSectionVM.notificationsCount != null && todoSectionVM.notificationsCount != "0") ? Positioned( // right: appState.isArabic() ? 8.w : -8.w, @@ -399,7 +385,20 @@ class _LandingPageState extends State { ), ) : SizedBox.shrink(), - ]), + ]).onPress(() async { + if (appState.isAuthenticated) { + notificationsViewModel.setNotificationStatusID(2); + notificationsViewModel.getAllNotifications(); + Navigator.of(context).push( + CustomPageRoute( + page: NotificationsListPage(), + // page: LoginScreen(), + ), + ); + } else { + await authVM.onLoginPressed(); + } + }), SizedBox( width: 24.w, ), From e80b6edf1e6e5e2848e7b9cb283d91c9b28d10f1 Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Sun, 30 Aug 2026 17:19:58 +0300 Subject: [PATCH 11/15] vital sign for appointment done. --- lib/core/api/api_client.dart | 2 +- lib/core/app_assets.dart | 1 + .../hmg_services/hmg_services_repo.dart | 7 +- .../hmg_services/hmg_services_view_model.dart | 19 +++- .../appointment_details_page.dart | 76 ++++++++++++++++ .../vital_sign/vital_sign_details_page.dart | 76 +++++++++++----- .../vital_sign/vital_sign_page.dart | 87 +++++++++++-------- 7 files changed, 204 insertions(+), 64 deletions(-) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 3a5ff281..4db0cf8b 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -210,7 +210,7 @@ class ApiClientImp implements ApiClient { body['TokenID'] = "@dm!n"; } - body['TokenID'] = "@dm!n"; + // body['TokenID'] = "@dm!n"; // body['PatientID'] = 1307867; // body['PatientID'] = 53320; // body['PatientTypeID'] = 1; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index b4611890..d5f46e82 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -110,6 +110,7 @@ class AppAssets { static const String report_icon = '$svgBasePath/report_icon.svg'; static const String radiology_icon = '$svgBasePath/radiology_icon.svg'; static const String prescription_item_icon = '$svgBasePath/prescription_item_icon.svg'; + static const String vital_sign_item_icon = '$svgBasePath/vital_sign_item_icon.svg'; static const String checkin_location_icon = '$svgBasePath/checkin_location_icon.svg'; static const String checkin_nfc_icon = '$svgBasePath/checkin_nfc_icon.svg'; static const String checkin_qr_icon = '$svgBasePath/checkin_qr_icon.svg'; diff --git a/lib/features/hmg_services/hmg_services_repo.dart b/lib/features/hmg_services/hmg_services_repo.dart index 8f439d2c..6e01a561 100644 --- a/lib/features/hmg_services/hmg_services_repo.dart +++ b/lib/features/hmg_services/hmg_services_repo.dart @@ -67,7 +67,7 @@ abstract class HmgServicesRepo { Future>> getCovidPaymentInfo(String procedureID, int projectID); - Future>>> getPatientVitalSign(); + Future>>> getPatientVitalSign( num? transID, int? projectID); } class HmgServicesRepoImp implements HmgServicesRepo { @@ -917,8 +917,11 @@ class HmgServicesRepoImp implements HmgServicesRepo { } @override - Future>>> getPatientVitalSign() async { + Future>>> getPatientVitalSign(num? transID, int? projectID) async { Map requestBody = { + "TransID": transID, + "TransNo": transID, + "ProjectID": projectID, }; try { diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart index da1ae80d..f462aedd 100644 --- a/lib/features/hmg_services/hmg_services_view_model.dart +++ b/lib/features/hmg_services/hmg_services_view_model.dart @@ -895,12 +895,23 @@ class HmgServicesViewModel extends ChangeNotifier { Future getPatientVitalSign({ Function(dynamic)? onSuccess, Function(String)? onError, + num? transNo, + int? projectID, + bool forceRefresh = false, }) async { - if (hasVitalSignDataLoaded) return; + if (hasVitalSignDataLoaded && transNo == null && projectID == null && !forceRefresh) { + if (onSuccess != null) { + onSuccess(GenericApiModel>( + messageStatus: 1, + data: vitalSignList, + )); + } + return; + } isVitalSignLoading = true; notifyListeners(); - final result = await hmgServicesRepo.getPatientVitalSign(); + final result = await hmgServicesRepo.getPatientVitalSign(transNo, projectID); result.fold( (failure) async { @@ -915,7 +926,9 @@ class HmgServicesViewModel extends ChangeNotifier { isVitalSignLoading = false; if (apiResponse.messageStatus == 1) { vitalSignList = apiResponse.data ?? []; - hasVitalSignDataLoaded = true; + if (transNo == null && projectID == null) { + hasVitalSignDataLoaded = true; + } if (vitalSignList.isNotEmpty) { getIt.get().setSelectedHeight(vitalSignList.first.heightCm); getIt.get().setSelectedWeight(vitalSignList.first.weightKg); diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 4d181230..5ba715e9 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -22,6 +22,9 @@ import 'package:hmg_patient_app_new/features/book_appointments/models/resp_model import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; import 'package:hmg_patient_app_new/features/contact_us/models/feedback_type.dart'; import 'package:hmg_patient_app_new/features/hospital/AppPermission.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/vital_sign_respo_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; @@ -40,6 +43,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appoin import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/bell_switch.dart'; import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart'; +import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_page.dart'; import 'package:hmg_patient_app_new/services/clarity_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -666,6 +670,76 @@ class _AppointmentDetailsPageState extends State { padding: EdgeInsets.zero, shrinkWrap: true, children: [ + MedicalFileCard( + label: LocaleKeys.vitalSigns.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.vital_sign_item_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() async { + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context)); + final hmgServicesVM = getIt.get(); + final appt = widget.patientAppointmentHistoryResponseModel; + final targetTransNo = appt.vitalSignAppointmentNo ?? appt.appointmentNo; + + await hmgServicesVM.getPatientVitalSign( + transNo: targetTransNo, + projectID: appt.projectID, + onSuccess: (apiResponse) { + LoaderBottomSheet.hideLoader(); + final List vitalList = (apiResponse is GenericApiModel>) + ? (apiResponse.data ?? []) + : (apiResponse.data is List + ? (apiResponse.data as List) + : hmgServicesVM.vitalSignList); + + VitalSignResModel? matchedVitalSign; + if (vitalList.isNotEmpty) { + // 1. Match by vitalSignAppointmentNo or appointmentNo + if (targetTransNo != null) { + final targetStr = targetTransNo.toString(); + for (final v in vitalList) { + if (v.transNo != null && v.transNo.toString() == targetStr) { + matchedVitalSign = v; + break; + } + } + } + + // 2. If specific API returned records for this TransNo/ProjectID, fallback to first + matchedVitalSign ??= vitalList.first; + + Navigator.of(context).push( + CustomPageRoute( + page: VitalSignPage( + specificVitalSign: matchedVitalSign, + isFromAppointment: true, + ), + ), + ); + } else { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: LocaleKeys.noVitalSignsRecordedYet.tr(context: context)), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + ); + }), MedicalFileCard( label: LocaleKeys.labResults.tr(context: context), textColor: AppColors.blackColor, @@ -790,6 +864,7 @@ class _AppointmentDetailsPageState extends State { // ), // ); }), + MedicalFileCard( label: LocaleKeys.doctorResponses.tr(context: context), textColor: AppColors.blackColor, @@ -945,6 +1020,7 @@ class _AppointmentDetailsPageState extends State { ); }) : SizedBox.shrink(), + ], ); }), diff --git a/lib/presentation/vital_sign/vital_sign_details_page.dart b/lib/presentation/vital_sign/vital_sign_details_page.dart index 63b21291..48532645 100644 --- a/lib/presentation/vital_sign/vital_sign_details_page.dart +++ b/lib/presentation/vital_sign/vital_sign_details_page.dart @@ -53,8 +53,15 @@ class VitalSignDetailsArgs { class VitalSignDetailsPage extends StatefulWidget { final VitalSignDetailsArgs args; - - const VitalSignDetailsPage({super.key, required this.args}); + final VitalSignResModel? selectedVitalSign; + final bool isFromAppointment; + + const VitalSignDetailsPage({ + super.key, + required this.args, + this.selectedVitalSign, + this.isFromAppointment = false, + }); @override State createState() => _VitalSignDetailsPageState(); @@ -65,17 +72,28 @@ class _VitalSignDetailsPageState extends State { VitalSignDetailsArgs get args => widget.args; + @override + void initState() { + super.initState(); + if (widget.isFromAppointment) { + WidgetsBinding.instance.addPostFrameCallback((_) { + getIt.get().getPatientVitalSign(forceRefresh: true); + }); + } + } + @override Widget build(BuildContext context) { return CollapsingListView( title: LocaleKeys.vitalSignDetails.tr(context: context), child: Consumer( builder: (context, viewModel, child) { - final latest = viewModel.vitalSignList.isNotEmpty ? viewModel.vitalSignList.first : null; + final targetVitalSign = widget.selectedVitalSign ?? + (viewModel.vitalSignList.isNotEmpty ? viewModel.vitalSignList.first : null); final history = _buildSeries(viewModel.vitalSignList, args); - final latestValueText = _latestValueText(latest); - final status = _statusForLatest(latest); + final latestValueText = _latestValueText(targetVitalSign); + final status = _statusForLatest(targetVitalSign); final scheme = VitalSignUiModel.scheme(status: status, label: args.title); return SingleChildScrollView( @@ -89,7 +107,7 @@ class _VitalSignDetailsPageState extends State { valueText: latestValueText, status: status, scheme: scheme, - latestDate: latest?.vitalSignDate, + latestDate: targetVitalSign?.vitalSignDate, ), _whatIsThisResultCard(context), _historyCard(context, history: history), @@ -531,6 +549,17 @@ class _VitalSignDetailsPageState extends State { return RangeAnnotations(horizontalRangeAnnotations: ranges); } + String _formatValue(dynamic value) { + if (value == null) return '--'; + if (value is num && value == 0) return '--'; + if (value is String) { + if (value.trim().isEmpty) return '--'; + final parsed = double.tryParse(value); + if (parsed == null || parsed == 0) return '--'; + } + return value.toString(); + } + List _buildSeries(List vitals, VitalSignDetailsArgs args) { final List points = []; @@ -578,7 +607,7 @@ class _VitalSignDetailsPageState extends State { DataPoint( value: index, label: label, - actualValue: mv.toStringAsFixed(0), + actualValue: _formatValue(mv), time: dt, displayTime: '${dt.day}/${dt.month}/${dt.year}', unitOfMeasurement: args.unit, @@ -618,7 +647,7 @@ class _VitalSignDetailsPageState extends State { DataPoint( value: index, label: label, - actualValue: diastolic.toStringAsFixed(0), + actualValue: _formatValue(diastolic), time: dt, displayTime: '${dt.day}/${dt.month}/${dt.year}', unitOfMeasurement: args.unit, @@ -646,28 +675,27 @@ class _VitalSignDetailsPageState extends State { switch (args.metric) { case VitalSignMetric.bmi: - final v = _toDouble(latest.bodyMassIndex); - return v == null ? '--' : v.toStringAsFixed(0); + return _formatValue(latest.bodyMassIndex); case VitalSignMetric.height: - final v = _toDouble(latest.heightCm); - return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + final formatted = _formatValue(latest.heightCm); + return formatted == '--' ? '--' : (args.unit.isNotEmpty ? '$formatted ${args.unit}' : formatted); case VitalSignMetric.weight: - final v = _toDouble(latest.weightKg); - return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + final formatted = _formatValue(latest.weightKg); + return formatted == '--' ? '--' : (args.unit.isNotEmpty ? '$formatted ${args.unit}' : formatted); case VitalSignMetric.temperature: - final v = _toDouble(latest.temperatureCelcius); - return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + final formatted = _formatValue(latest.temperatureCelcius); + return formatted == '--' ? '--' : (args.unit.isNotEmpty ? '$formatted ${args.unit}' : formatted); case VitalSignMetric.heartRate: - final v = _toDouble(latest.heartRate ?? latest.pulseBeatPerMinute); - return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + final formatted = _formatValue(latest.heartRate ?? latest.pulseBeatPerMinute); + return formatted == '--' ? '--' : (args.unit.isNotEmpty ? '$formatted ${args.unit}' : formatted); case VitalSignMetric.respiratoryRate: - final v = _toDouble(latest.respirationBeatPerMinute); - return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + final formatted = _formatValue(latest.respirationBeatPerMinute); + return formatted == '--' ? '--' : (args.unit.isNotEmpty ? '$formatted ${args.unit}' : formatted); case VitalSignMetric.bloodPressure: - final s = _toDouble(latest.bloodPressureHigher); - final d = _toDouble(latest.bloodPressureLower); - if (s == null || d == null) return '--'; - return '${s.toStringAsFixed(0)}/${d.toStringAsFixed(0)}'; + final s = _formatValue(latest.bloodPressureHigher); + final d = _formatValue(latest.bloodPressureLower); + if (s == '--' || d == '--') return '--'; + return '$s/$d'; } } diff --git a/lib/presentation/vital_sign/vital_sign_page.dart b/lib/presentation/vital_sign/vital_sign_page.dart index 826c3971..2be990f7 100644 --- a/lib/presentation/vital_sign/vital_sign_page.dart +++ b/lib/presentation/vital_sign/vital_sign_page.dart @@ -21,7 +21,14 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; class VitalSignPage extends StatefulWidget { - const VitalSignPage({super.key}); + final VitalSignResModel? specificVitalSign; + final bool isFromAppointment; + + const VitalSignPage({ + super.key, + this.specificVitalSign, + this.isFromAppointment = false, + }); @override State createState() => _VitalSignPageState(); @@ -53,10 +60,14 @@ class _VitalSignPageState extends State { return value.toString(); } - void _openDetails(VitalSignDetailsArgs args) { + void _openDetails(VitalSignDetailsArgs args, VitalSignResModel? targetVitalSign) { Navigator.of(context).push( CustomPageRoute( - page: VitalSignDetailsPage(args: args), + page: VitalSignDetailsPage( + args: args, + selectedVitalSign: targetVitalSign, + isFromAppointment: widget.isFromAppointment, + ), ), ); } @@ -75,20 +86,21 @@ class _VitalSignPageState extends State { child: Consumer( builder: (context, viewModel, child) { - // Get the latest vital sign data (first item in the list) - VitalSignResModel? latestVitalSign = viewModel.vitalSignList.isNotEmpty - ? viewModel.vitalSignList.first - : null; + // Get target vital sign: specificVitalSign if passed, otherwise first in list + VitalSignResModel? targetVitalSign = widget.specificVitalSign ?? + (viewModel.vitalSignList.isNotEmpty + ? viewModel.vitalSignList.first + : null); // Debug logging for blood pressure - // if (latestVitalSign != null) { + // if (targetVitalSign != null) { // print('=== Blood Pressure Debug ==='); - // print('bloodPressureHigher: ${latestVitalSign.bloodPressureHigher}'); - // print('bloodPressureLower: ${latestVitalSign.bloodPressureLower}'); - // print('bloodPressureHigher type: ${latestVitalSign.bloodPressureHigher.runtimeType}'); - // print('bloodPressureLower type: ${latestVitalSign.bloodPressureLower.runtimeType}'); - // print('bloodPressureHigher == 0: ${latestVitalSign.bloodPressureHigher == 0}'); - // print('bloodPressureLower == 0: ${latestVitalSign.bloodPressureLower == 0}'); + // print('bloodPressureHigher: ${targetVitalSign.bloodPressureHigher}'); + // print('bloodPressureLower: ${targetVitalSign.bloodPressureLower}'); + // print('bloodPressureHigher type: ${targetVitalSign.bloodPressureHigher.runtimeType}'); + // print('bloodPressureLower type: ${targetVitalSign.bloodPressureLower.runtimeType}'); + // print('bloodPressureHigher == 0: ${targetVitalSign.bloodPressureHigher == 0}'); + // print('bloodPressureLower == 0: ${targetVitalSign.bloodPressureLower == 0}'); // print('========================'); // } @@ -112,9 +124,9 @@ class _VitalSignPageState extends State { _buildVitalSignCard( icon: AppAssets.bmiVital, label: LocaleKeys.bmi.tr(context: context), - value: _formatValue(latestVitalSign?.bodyMassIndex), + value: _formatValue(targetVitalSign?.bodyMassIndex), unit: '', - status: VitalSignUiModel.bmiStatus(latestVitalSign?.bodyMassIndex), + status: VitalSignUiModel.bmiStatus(targetVitalSign?.bodyMassIndex), onTap: () => _openDetails( VitalSignDetailsArgs( metric: VitalSignMetric.bmi, @@ -122,6 +134,7 @@ class _VitalSignPageState extends State { icon: AppAssets.bmiVital, unit: '', ), + targetVitalSign, ), ), SizedBox(height: 16.h), @@ -130,7 +143,7 @@ class _VitalSignPageState extends State { _buildVitalSignCard( icon: AppAssets.heightVital, label: LocaleKeys.height.tr(context: context), - value: _formatValue(latestVitalSign?.heightCm), + value: _formatValue(targetVitalSign?.heightCm), unit: 'cm', status: null, onTap: () => _openDetails( @@ -140,6 +153,7 @@ class _VitalSignPageState extends State { icon: AppAssets.heightVital, unit: 'cm', ), + targetVitalSign, ), ), SizedBox(height: 16.h), @@ -148,10 +162,10 @@ class _VitalSignPageState extends State { _buildVitalSignCard( icon: AppAssets.weightVital, label: LocaleKeys.weight.tr(context: context), - value: _formatValue(latestVitalSign?.weightKg), + value: _formatValue(targetVitalSign?.weightKg), unit: 'kg', - status: (latestVitalSign?.weightKg != null) - ? VitalSignUiModel.weightStatus(latestVitalSign?.bodyMassIndex) + status: (targetVitalSign?.weightKg != null) + ? VitalSignUiModel.weightStatus(targetVitalSign?.bodyMassIndex) : null, onTap: () => _openDetails( VitalSignDetailsArgs( @@ -160,6 +174,7 @@ class _VitalSignPageState extends State { icon: AppAssets.weightVital, unit: 'kg', ), + targetVitalSign, ), ), SizedBox(height: 16.h), @@ -168,18 +183,18 @@ class _VitalSignPageState extends State { _buildVitalSignCard( icon: AppAssets.bloodPressure, label: LocaleKeys.bloodPressure.tr(context: context), - value: (latestVitalSign != null && - _isValidValue(latestVitalSign.bloodPressureHigher) && - _isValidValue(latestVitalSign.bloodPressureLower)) - ? '${latestVitalSign.bloodPressureHigher}/${latestVitalSign.bloodPressureLower}' + value: (targetVitalSign != null && + _isValidValue(targetVitalSign.bloodPressureHigher) && + _isValidValue(targetVitalSign.bloodPressureLower)) + ? '${targetVitalSign.bloodPressureHigher}/${targetVitalSign.bloodPressureLower}' : '--', unit: '', - status: (latestVitalSign != null && - _isValidValue(latestVitalSign.bloodPressureHigher) && - _isValidValue(latestVitalSign.bloodPressureLower)) + status: (targetVitalSign != null && + _isValidValue(targetVitalSign.bloodPressureHigher) && + _isValidValue(targetVitalSign.bloodPressureLower)) ? VitalSignUiModel.bloodPressureStatus( - systolic: latestVitalSign.bloodPressureHigher, - diastolic: latestVitalSign.bloodPressureLower, + systolic: targetVitalSign.bloodPressureHigher, + diastolic: targetVitalSign.bloodPressureLower, ) : null, onTap: () => _openDetails( @@ -191,6 +206,7 @@ class _VitalSignPageState extends State { low: 90, high: 140, ), + targetVitalSign, ), ), SizedBox(height: 16.h), @@ -199,7 +215,7 @@ class _VitalSignPageState extends State { _buildVitalSignCard( icon: AppAssets.temperature, label: LocaleKeys.temperature.tr(context: context), - value: _formatValue(latestVitalSign?.temperatureCelcius), + value: _formatValue(targetVitalSign?.temperatureCelcius), unit: '°C', status: null, onTap: () => _openDetails( @@ -211,6 +227,7 @@ class _VitalSignPageState extends State { low: 36.1, high: 37.2, ), + targetVitalSign, ), ), ], @@ -275,9 +292,9 @@ class _VitalSignPageState extends State { child: _buildVitalSignCard( icon: AppAssets.heart, label: LocaleKeys.heart.tr(context: context), - value: _formatValue(latestVitalSign?.heartRate ?? latestVitalSign?.pulseBeatPerMinute), + value: _formatValue(targetVitalSign?.heartRate ?? targetVitalSign?.pulseBeatPerMinute), unit: 'bpm', - status: _isValidValue(latestVitalSign?.heartRate ?? latestVitalSign?.pulseBeatPerMinute) ? VitalSignStatus.normal : null, + status: _isValidValue(targetVitalSign?.heartRate ?? targetVitalSign?.pulseBeatPerMinute) ? VitalSignStatus.normal : null, onTap: () => _openDetails( VitalSignDetailsArgs( metric: VitalSignMetric.heartRate, @@ -287,6 +304,7 @@ class _VitalSignPageState extends State { low: 60, high: 100, ), + targetVitalSign, ), ), ), @@ -299,9 +317,9 @@ class _VitalSignPageState extends State { _buildVitalSignCard( icon: AppAssets.respRate, label: LocaleKeys.respirationRate.tr(context: context), - value: _formatValue(latestVitalSign?.respirationBeatPerMinute), + value: _formatValue(targetVitalSign?.respirationBeatPerMinute), unit: 'bpm', - status: _isValidValue(latestVitalSign?.respirationBeatPerMinute) ? VitalSignStatus.normal : null, + status: _isValidValue(targetVitalSign?.respirationBeatPerMinute) ? VitalSignStatus.normal : null, onTap: () => _openDetails( VitalSignDetailsArgs( metric: VitalSignMetric.respiratoryRate, @@ -311,6 +329,7 @@ class _VitalSignPageState extends State { low: 12, high: 20, ), + targetVitalSign, ), ), ], From b32c15a88693c1426949ebf989b730b16977f6eb Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 1 Sep 2026 09:47:19 +0300 Subject: [PATCH 12/15] updates --- lib/features/paytabs/paytabs_view_model.dart | 5 +++++ .../appointments/widgets/appointment_card.dart | 1 + .../livecare/immediate_livecare_payment_page.dart | 8 +++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/features/paytabs/paytabs_view_model.dart b/lib/features/paytabs/paytabs_view_model.dart index a7894f07..35d3c85b 100644 --- a/lib/features/paytabs/paytabs_view_model.dart +++ b/lib/features/paytabs/paytabs_view_model.dart @@ -52,6 +52,11 @@ class PayTabsViewModel extends ChangeNotifier { profileId: paytabsProjectDetailsRespModel!.merchantIdentifier, serverKey: paytabsProjectDetailsRespModel!.accessCode, clientKey: paytabsProjectDetailsRespModel!.shaResponse, + + // profileId: PaymentSdkDefaultConfig.profileID, + // serverKey: PaymentSdkDefaultConfig.serverKey, + // clientKey: PaymentSdkDefaultConfig.clientKey, + transactionType: PaymentSdkTransactionType.SALE, cartId: cartID.substring((cartID.length - 5), cartID.length), cartDescription: paymentDescription, diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 1cbe3be2..38c6c7d3 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -637,6 +637,7 @@ class _AppointmentCardState extends State { } void _goToDetails(BuildContext context) { + if (widget.isLoading) return; if (widget.isFromMedicalReport) return; if (widget.isForEyeMeasurements) { Navigator.of(context).push( diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart index 692e8b44..96e38363 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart @@ -817,7 +817,13 @@ class _ImmediateLiveCarePaymentPageState extends State Date: Tue, 1 Sep 2026 09:55:00 +0300 Subject: [PATCH 13/15] no message --- assets/images/svg/vital_sign_item_icon.svg | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 assets/images/svg/vital_sign_item_icon.svg diff --git a/assets/images/svg/vital_sign_item_icon.svg b/assets/images/svg/vital_sign_item_icon.svg new file mode 100644 index 00000000..3ebe220a --- /dev/null +++ b/assets/images/svg/vital_sign_item_icon.svg @@ -0,0 +1,7 @@ + + + + + + + From 81a07e4d3f2003ece98ceb7b729de8c725df14f7 Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Tue, 1 Sep 2026 11:22:29 +0300 Subject: [PATCH 14/15] vital sign bug fix --- .../hmg_services/hmg_services_view_model.dart | 37 ++++++++++++------- .../appointment_details_page.dart | 2 +- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart index f462aedd..9cd7e519 100644 --- a/lib/features/hmg_services/hmg_services_view_model.dart +++ b/lib/features/hmg_services/hmg_services_view_model.dart @@ -899,7 +899,9 @@ class HmgServicesViewModel extends ChangeNotifier { int? projectID, bool forceRefresh = false, }) async { - if (hasVitalSignDataLoaded && transNo == null && projectID == null && !forceRefresh) { + final bool isSpecificQuery = (transNo != null || projectID != null); + + if (!isSpecificQuery && hasVitalSignDataLoaded && !forceRefresh) { if (onSuccess != null) { onSuccess(GenericApiModel>( messageStatus: 1, @@ -908,37 +910,44 @@ class HmgServicesViewModel extends ChangeNotifier { } return; } - isVitalSignLoading = true; - notifyListeners(); + + if (!isSpecificQuery) { + isVitalSignLoading = true; + notifyListeners(); + } final result = await hmgServicesRepo.getPatientVitalSign(transNo, projectID); result.fold( (failure) async { - isVitalSignLoading = false; - notifyListeners(); + if (!isSpecificQuery) { + isVitalSignLoading = false; + notifyListeners(); + } await errorHandlerService.handleError(failure: failure); if (onError != null) { onError(failure.message); } }, (apiResponse) { - isVitalSignLoading = false; - if (apiResponse.messageStatus == 1) { - vitalSignList = apiResponse.data ?? []; - if (transNo == null && projectID == null) { + if (!isSpecificQuery) { + isVitalSignLoading = false; + if (apiResponse.messageStatus == 1) { + vitalSignList = apiResponse.data ?? []; hasVitalSignDataLoaded = true; - } - if (vitalSignList.isNotEmpty) { - getIt.get().setSelectedHeight(vitalSignList.first.heightCm); - getIt.get().setSelectedWeight(vitalSignList.first.weightKg); + if (vitalSignList.isNotEmpty) { + getIt.get().setSelectedHeight(vitalSignList.first.heightCm); + getIt.get().setSelectedWeight(vitalSignList.first.weightKg); + } } notifyListeners(); + } + + if (apiResponse.messageStatus == 1) { if (onSuccess != null) { onSuccess(apiResponse); } } else { - notifyListeners(); if (onError != null) { onError(apiResponse.errorMessage ?? 'Unknown error'); } diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index e384793b..dc147981 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -692,7 +692,7 @@ class _AppointmentDetailsPageState extends State { ? (apiResponse.data ?? []) : (apiResponse.data is List ? (apiResponse.data as List) - : hmgServicesVM.vitalSignList); + : []); VitalSignResModel? matchedVitalSign; if (vitalList.isNotEmpty) { From de75c6adc1d2a40dabc8a6a0f8c5145e7f74236e Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 1 Sep 2026 14:13:00 +0300 Subject: [PATCH 15/15] Updates --- .../authentication/authentication_view_model.dart | 4 ++++ .../medical_file/medical_file_view_model.dart | 10 ++++++++++ .../prescriptions/prescriptions_view_model.dart | 5 +++++ .../livecare/immediate_livecare_payment_page.dart | 15 ++++++++++++--- .../medical_file/medical_file_page.dart | 2 -- 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 72ca42cb..a8719f29 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -31,6 +31,7 @@ import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_mode import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; @@ -1280,6 +1281,9 @@ class AuthenticationViewModel extends ChangeNotifier { getIt.get().setIsWalletAmountToBeLoaded(true); getIt.get().setIsInsuranceDataToBeLoaded(true); getIt.get().setHasVitalSignDataLoaded(false); + getIt.get().setIsSickLeavesDataNeedsReloading(true); + getIt.get().setIsMedicalReportsDataNeedsReloading(true); + getIt.get().setIsPrescriptionDataNeedsReloading(true); if (isUserAgreedBefore) { LoaderBottomSheet.hideLoader(); navigateToHomeScreen(); diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index a9bfb09c..29f653ec 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -168,6 +168,16 @@ class MedicalFileViewModel extends ChangeNotifier { notifyListeners(); } + setIsSickLeavesDataNeedsReloading(bool value) { + isSickLeavesDataNeedsReloading = value; + notifyListeners(); + } + + setIsMedicalReportsDataNeedsReloading(bool value) { + isMedicalReportsDataNeedsReloading = value; + notifyListeners(); + } + setIsPatientSickLeaveListLoading(bool val) { if (val && isSickLeavesDataNeedsReloading) { patientSickLeaveList.clear(); diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index d75b3f8e..1e33b509 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -78,6 +78,11 @@ class PrescriptionsViewModel extends ChangeNotifier { notifyListeners(); } + void setIsPrescriptionDataNeedsReloading(bool value) { + isPrescriptionsDataNeedsReloading = value; + notifyListeners(); + } + Future checkIfReminderExistForPrescription(int index) async { final item = prescriptionDetailsList[index]; // Check device calendar diff --git a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart index 96e38363..31defa56 100644 --- a/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart +++ b/lib/presentation/book_appointment/livecare/immediate_livecare_payment_page.dart @@ -819,14 +819,23 @@ class _ImmediateLiveCarePaymentPageState extends State { dividerColor: AppColors.dividerColor, itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h), onItemToggled: (index, isExpanded) { - debugPrint("Item at index $index is now ${isExpanded ? 'expanded' : 'collapsed'}"); - if (index == 0 && isExpanded) { myAppointmentsViewModel.getPatientMyDoctors(isTop8: true); prescriptionsViewModel.initPrescriptionsViewModel();