Merge pull request 'HMG x Infermedica (faiz_dev_symptoms_checker)' (#327) from faiz_dev_symptoms_checker into master

Reviewed-on: https://34.17.182.140/Haroon6138/HMG_Patient_App_New/pulls/327
pull/328/head
Haroon6138 2 days ago
commit cdc6cf1fec

@ -381,6 +381,12 @@
"clickHere": "انقر هنا",
"iAgreeToTheTermsAndConditions": "أوافق على الشروط والأحكام ",
"iAgreeToTheTermsAndConditionsSubtitle": "أوافق على الشروط والأحكام ",
"iAgreeAndAcceptTermsAndConditions": "أوافق وأقبل ",
"termsAndConditions": "الشروط والأحكام",
"iAgreeHealthInfoCanBeUsed": "أوافق على استخدام معلوماتي الصحية للمقابلة. مزيد من المعلومات في ",
"privacyPolicyText": "سياسة الخصوصية",
"outPatient": "العيادات الخارجيه",
"thisInvoiceIsNotEligibleForRefund": "هذه الفاتورة غير مؤهلة لاسترداد الأموال",
"save": "حفظ",
"userAgreement": "اتفاقية المستخدم",
"updateSuccessfully": "تم التحديث بنجاح",
@ -1440,6 +1446,9 @@
"symptomsSelector": "محدد الأعراض",
"possibleSymptomsRelatedTo": "أعراض مرتبطة بـ",
"suggestionAIListDescription": "هذه قائمة بالأعراض المقترحة من قبل الذكاء الاصطناعي، بناءً على المعلومات التي تم جمعها حتى الآن خلال المقابلة",
"whyAmIBeingAskedThis": "لماذا يُطرح علي هذا السؤال؟",
"weAreAskingThisQuestionToBetterUnderstand": "نطرح هذا السؤال لفهم {symptoms} بشكل أفضل وتحسين دقة التقييم.",
"analyzingYourSymptoms": "أعراضك",
"analyzing": "جاري التحليل...",
"seekMedicalAdvice": "اطلب المشورة الطبية",
"monitorDescription": "لا حاجة لطلب المشورة الطبية. فقط حافظ على روتين صحي.",
@ -1458,6 +1467,30 @@
"areYouSureYouWantToExitProgress": "هل تود الخروج؟ ستفقد تقدمك الحالي.",
"noQuestionAvailable": "لا يوجد سؤال متاح",
"possibleSymptom": "تشخيص محتمل",
"triageLevelEmergencyAmbulance": "طوارئ - اتصل بالإسعاف",
"triageLevelEmergency": "طوارئ",
"triageLevelConsultation24": "استشارة خلال 24 ساعة",
"triageLevelConsultation": "استشارة طبية",
"triageLevelSelfCare": "رعاية ذاتية",
"triageLevelEmergencyAmbulanceDesc": "الأعراض المبلغ عنها خطيرة جدًا وقد تحتاج إلى رعاية طارئة. يجب عليك الاتصال بالإسعاف الآن.",
"triageLevelEmergencyDesc": "تبدو الأدلة المبلغ عنها خطيرة ويجب عليك الذهاب إلى قسم الطوارئ. إذا لم تتمكن من ال<D8A7><D984>صول إلى أقرب قسم طوارئ، اتصل بالإسعاف.",
"triageLevelConsultation24Desc": "يجب عليك زيارة الطبيب خلال 24 ساعة. إذا ساءت الأعراض فجأة، توجه إلى أقرب قسم طوارئ.",
"triageLevelConsultationDesc": "قد تحتاج إلى تقييم طبي وقد تحتاج إلى تحديد موعد مع طبيب. إذا ساءت الأعراض، راجع الطبيب على الفور.",
"triageLevelSelfCareDesc": "قد لا تتطلب الأعراض المعلنة تقييمًا طبيًا وعادة ما تزول من تلقاء نفسها. في بعض الأحيان يمكن علاجها من خلال طرق الرعاية الذاتية. راقب أعراضك واستشر الطبيب إذا ساءت الأعراض أو ظهرت أعراض جديدة.",
"rootCauseEmergencyEvidencePresent": "تم الإبلاغ عن دليل طوارئ",
"rootCauseSeriousEvidencePresent": "تم الإبلاغ عن دليل خطير",
"rootCauseEmergencyConditionLikely": "حالة تهدد الحياة محتملة",
"rootCauseEmergencyConditionPossible": "حالة تهدد الحياة ممكنة",
"rootCauseConsultationConditionLikely": "استشارة طبية مطلوبة",
"rootCauseSelfCareSufficient": "لا يوجد سبب محدد للتقييم الطبي",
"rootCauseDiagnosisUnknown": "التقييم غير ممكن",
"teleconsultationAvailable": "الاستشارة عن بُعد متاحة",
"seriousSymptoms": "أعراض خطيرة",
"howTo": "كيفية القياس؟",
"explainWhy": "اشرح، لماذا؟",
"whyThisCondition": "لماذا هذه الحالة؟",
"supportingEvidence": "الأدلة الداعمة",
"explanationDescription": "بناءً على الأعراض التي أبلغت عنها، تم تحديد هذه الحالة كتطابق محتمل. الأعراض التالية تدعم هذا التقييم:",
"symptomsCheckerFindingScore": "نسبة التطابق",
"notSet": "غير محدد",
"years": "سنوات",
@ -1907,6 +1940,8 @@
"advanceNumber": "رقم الدفعة المقدمة",
"amountOnly": "المبلغ",
"invoiceHistory": "سجل الفواتير",
"refund": "استرداد",
"invoiceHistory": "سجل الفواتير",
"thisInvoiceIsNotEligibleForRefund": "هذه الفاتورة غير مؤهلة للاسترداد",
"refund": "استرداد"
}

@ -377,6 +377,12 @@
"clickHere": "Click here",
"iAgreeToTheTermsAndConditions": "I agree to the terms and conditions ",
"iAgreeToTheTermsAndConditionsSubtitle": "I agree to the terms and conditions ",
"iAgreeAndAcceptTermsAndConditions": "I agree and accept ",
"termsAndConditions": "Terms and Conditions",
"iAgreeHealthInfoCanBeUsed": "I agree that my health information can be used for the interview. More information in the ",
"privacyPolicyText": "Privacy Policy",
"outPatient": "Out Patient",
"thisInvoiceIsNotEligibleForRefund": "This invoice is not eligible for refund",
"save": "Save",
"userAgreement": "User Agreement",
"updateSuccessfully": "Update Successfully",
@ -1432,6 +1438,9 @@
"symptomsSelector": "Symptoms Selector",
"possibleSymptomsRelatedTo": "Possible symptoms related to",
"suggestionAIListDescription": "This is a list of symptoms suggested by our AI, based on the information gathered so far during the interview",
"whyAmIBeingAskedThis": "Why am I being asked this?",
"weAreAskingThisQuestionToBetterUnderstand": "We are asking this question to better understand your {symptoms} and improve the accuracy of the assessment.",
"analyzingYourSymptoms": "your symptoms",
"analyzing": "Analyzing...",
"seekMedicalAdvice": "Seek Medical Advice",
"monitorDescription": "No need to seek medical advice. Just keep healthy routine.",
@ -1450,6 +1459,30 @@
"areYouSureYouWantToExitProgress": "Are you sure you want to exit? Your progress will be lost.",
"noQuestionAvailable": "No question available",
"possibleSymptom": "Possible symptom: ",
"triageLevelEmergencyAmbulance": "Emergency - Call Ambulance",
"triageLevelEmergency": "Emergency",
"triageLevelConsultation24": "Consultation Within 24 Hours",
"triageLevelConsultation": "Medical Consultation",
"triageLevelSelfCare": "Self-Care",
"triageLevelEmergencyAmbulanceDesc": "The reported symptoms are very serious and you may require emergency care. You should call an ambulance right now.",
"triageLevelEmergencyDesc": "The reported evidence appears serious and you should go to an emergency department. If you can't get to the nearest emergency department, call an ambulance.",
"triageLevelConsultation24Desc": "You should see a doctor within 24 hours. If the symptoms suddenly get worse, go to the nearest emergency department.",
"triageLevelConsultationDesc": "You may require medical evaluation and may need to schedule an appointment with a doctor. If the symptoms get worse, see a doctor immediately.",
"triageLevelSelfCareDesc": "The declared symptoms may not require a medical evaluation and they usually resolve on their own. Sometimes they can be treated through self-care methods. Observe your symptoms and consult a doctor if the symptoms get worse or new ones appear.",
"rootCauseEmergencyEvidencePresent": "Emergency evidence reported",
"rootCauseSeriousEvidencePresent": "Serious evidence reported",
"rootCauseEmergencyConditionLikely": "Life-threatening condition likely",
"rootCauseEmergencyConditionPossible": "Life-threatening condition possible",
"rootCauseConsultationConditionLikely": "Medical consultation required",
"rootCauseSelfCareSufficient": "No identified reason for medical evaluation",
"rootCauseDiagnosisUnknown": "Assessment not possible",
"teleconsultationAvailable": "Teleconsultation Available",
"seriousSymptoms": "Serious Symptoms",
"howTo": "How to?",
"explainWhy": "Explain, Why?",
"whyThisCondition": "Why this condition?",
"supportingEvidence": "Supporting Evidence",
"explanationDescription": "Based on your reported symptoms, this condition has been identified as a possible match. The following symptoms support this assessment:",
"symptomsCheckerFindingScore": "- Symptoms checker finding score",
"notSet": "Not set",
"years": "Years",
@ -1900,7 +1933,6 @@
"advanceNumber": "Advance Number",
"amountOnly": "Amount",
"invoiceHistory": "Invoice History"
}

@ -207,7 +207,7 @@ class ApiClientImp implements ApiClient {
if (url == 'https://uat.hmgwebservices.com/Services/NHIC.svc/REST/GetPatientInfo') {
url = "https://hmgwebservices.com/Services/NHIC.svc/REST/GetPatientInfo";
body['TokenID'] = "@dm!n";
}
}
// body['TokenID'] = "@dm!n";
// body['PatientID'] = 4774167;
@ -259,7 +259,7 @@ class ApiClientImp implements ApiClient {
body: requestBody,
headers: headers,
);
// log("response: ${response.body.toString()}");
log("response: ${response.body.toString()}");
} on SocketException catch (e) {
final message = e.message.contains('Connection reset by peer') ? LocaleKeys.networkConnectionReset.tr() : LocaleKeys.networkErrorMessage.tr();
onFailure(message, -1, failureType: ConnectivityFailure(message));

@ -4,7 +4,7 @@ import 'package:hmg_patient_app_new/core/enums.dart';
class ApiConsts {
static const maxSmallScreen = 660;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat;
// static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT
@ -12,7 +12,7 @@ class ApiConsts {
static String rcBaseUrl = 'https://rc.hmg.com/'; // dRC API URL PROD
static String hmgPharmacyApiBaseUrl = 'https://hmgpharmacyapi.hmg.com/'; // symptoms API URL PROD
static String symptomsCheckerApiUAT = '${hmgPharmacyApiBaseUrl}symptomsapi/api/SymptomChecker'; // dRC API URL PROD
static String symptomsCheckerApiUAT = '${hmgPharmacyApiBaseUrl}symptomsapi_live/api/SymptomChecker'; // dRC API URL PROD
static String symptomsCheckerApiLive = '${hmgPharmacyApiBaseUrl}symptomsapi_live/api/SymptomChecker'; // dRC API URL PROD
static String symptomsCheckerApi = '${hmgPharmacyApiBaseUrl}symptomsapi_live/api/SymptomChecker'; // dRC API URL PROD
static String symptomsCheckerScheduleAppointment = '$symptomsCheckerApi/ScheduleAppointment';
@ -211,6 +211,9 @@ class ApiConsts {
static final String diagnosis = '$symptomsCheckerApi/GetDiagnosis';
static final String explain = '$symptomsCheckerApi/ExplainDiagnosisResult';
static final String getClinicFromCondition = '$symptomsCheckerApi/GetClinicsByCondition?condition=';
static final String getRationale = '$symptomsCheckerApi/GetRationale';
static final String getTriage = '$symptomsCheckerApi/GetTriage';
static final String getExplanations = '$symptomsCheckerApi/GetExplanations';
//E-REFERRAL SERVICES
static final getAllRelationshipTypes = "Services/Patients.svc/REST/GetAllRelationshipTypes";
@ -1013,33 +1016,9 @@ class ApiKeyConstants {
static final String googleMapsApiKey = 'AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng';
}
//flutter: URL : https://hmgwebservices.com/Services/H2ORemainder.svc/REST/H2O_InsertUserActivity
// flutter: {"IdentificationNo":"2530976584","MobileNumber":"504278212","QuantityIntake":200,"VersionID":20.0,"Channel":3,"LanguageID":2,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","PatientOutSA":0,"SessionID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","isDentalAllowedBackend":false,"DeviceTypeID":1,"PatientID":4515697,"TokenID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","PatientTypeID":1,"PatientType":1,"Latitude":37.785834,"Longitude":-122.406417}
// flutter: response.body:
// flutter: {"Date":null,"LanguageID":0,"ServiceName":0,"Time":null,"AndroidLink":null,"AuthenticationTokenID":null,"Data":null,"Dataw":false,"DietType":0,"DietTypeID":0,"ErrorCode":null,"ErrorEndUserMessage":null,"ErrorEndUserMessageN":null,"ErrorMessage":null,"ErrorStatusCode":0,"ErrorType":0,"FoodCategory":0,"IOSLink":null,"IsAuthenticated":true,"MealOrderStatus":0,"MealType":0,"MessageStatus":1,"NumberOfResultRecords":0,"PatientBlodType":null,"SuccessMsg":null,"SuccessMsgN":null,"VidaUpdatedResponse":null,"IsHMGPatient":false,"LogInTokenID":null,"PhysicalActivityData":null,"RowExists":0,"UserDetailData":null,"UserDetailData_New":null,"UserProgressForMonthData":null,"UserProgressForTodayData":[{"QuantityConsumed":200.00,"PercentageConsumed":9.41,"PercentageLeft":90.59,"QuantityLimit":2125.00}],"UserProgressForWeekData":null,"UserProgressHistoryData":[{"Quantity":200.000,"CreatedDate":"\/Date(1766911222217+0300)\/"}],"VerificationCode":null,"isSMSSent":false}
// URL : https://hmgwebservices.com/Services/H2ORemainder.svc/REST/H2o_UndoUserActivity
// flutter: {"Progress":1,"MobileNumber":"504278212","IdentificationNo":"2530976584","VersionID":20.0,"Channel":3,"LanguageID":2,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","PatientOutSA":0,"SessionID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","isDentalAllowedBackend":false,"DeviceTypeID":1,"PatientID":4515697,"TokenID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","PatientTypeID":1,"PatientType":1,"Latitude":37.785834,"Longitude":-122.406417}
// flutter: response.body:
// flutter: {"Date":null,"LanguageID":0,"ServiceName":0,"Time":null,"AndroidLink":null,"AuthenticationTokenID":null,"Data":null,"Dataw":false,"DietType":0,"DietTypeID":0,"ErrorCode":null,"ErrorEndUserMessage":null,"ErrorEndUserMessageN":null,"ErrorMessage":null,"ErrorStatusCode":0,"ErrorType":0,"FoodCategory":0,"IOSLink":null,"IsAuthenticated":false,"MealOrderStatus":0,"MealType":0,"MessageStatus":1,"NumberOfResultRecords":0,"PatientBlodType":null,"SuccessMsg":null,"SuccessMsgN":null,"VidaUpdatedResponse":null,"IsHMGPatient":false,"LogInTokenID":null,"PhysicalActivityData":null,"RowExists":0,"UserDetailData":null,"UserDetailData_New":null,"UserProgressForMonthData":null,"UserProgressForTodayData":[{"QuantityConsumed":0.00,"PercentageConsumed":0.00,"PercentageLeft":100.00,"QuantityLimit":2125.00}],"UserProgressForWeekData":null,"UserProgressHistoryData":[],"VerificationCode":null,"isSMSSent":false}
// Progress":2 means weekly data
// flutter: URL : https://hmgwebservices.com/Services/H2ORemainder.svc/REST/H2O_GetUserProgress
// flutter: {"Progress":2,"MobileNumber":"504278212","IdentificationNo":"2530976584","VersionID":20.0,"Channel":3,"LanguageID":2,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","PatientOutSA":0,"SessionID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","isDentalAllowedBackend":false,"DeviceTypeID":1,"PatientID":4515697,"TokenID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","PatientTypeID":1,"PatientType":1,"Latitude":37.785834,"Longitude":-122.406417}
// flutter: response.body:
// [log] {"Date":null,"LanguageID":0,"ServiceName":0,"Time":null,"AndroidLink":null,"AuthenticationTokenID":null,"Data":null,"Dataw":false,"DietType":0,"DietTypeID":0,"ErrorCode":null,"ErrorEndUserMessage":null,"ErrorEndUserMessageN":null,"ErrorMessage":null,"ErrorStatusCode":0,"ErrorType":0,"FoodCategory":0,"IOSLink":null,"IsAuthenticated":false,"MealOrderStatus":0,"MealType":0,"MessageStatus":1,"NumberOfResultRecords":0,"PatientBlodType":null,"SuccessMsg":null,"SuccessMsgN":null,"VidaUpdatedResponse":null,"IsHMGPatient":false,"LogInTokenID":null,"PhysicalActivityData":null,"RowExists":0,"UserDetailData":null,"UserDetailData_New":null,"UserProgressForMonthData":null,"UserProgressForTodayData":null,"UserProgressForWeekData":[{"DayNumber":1,"DayDate":null,"DayName":"Sunday","PercentageConsumed":0},{"DayNumber":7,"DayDate":null,"DayName":"Saturday","PercentageConsumed":0},{"DayNumber":6,"DayDate":null,"DayName":"Friday","PercentageConsumed":0},{"DayNumber":5,"DayDate":null,"DayName":"Thursday","PercentageConsumed":0},{"DayNumber":4,"DayDate":null,"DayName":"Wednesday","PercentageConsumed":0},{"DayNumber":3,"DayDate":null,"DayName":"Tuesday","PercentageConsumed":0},{"DayNumber":2,"DayDate":null,"DayName":"Monday","PercentageConsumed":0}],"UserProgressHistoryData":[],"VerificationCode":null,"isSMSSent":false}
// Progress":1 means daily data
//URL : https://hmgwebservices.com/Services/H2ORemainder.svc/REST/H2O_GetUserProgress
// flutter: {"Progress":1,"MobileNumber":"504278212","IdentificationNo":"2530976584","VersionID":20.0,"Channel":3,"LanguageID":2,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","PatientOutSA":0,"SessionID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","isDentalAllowedBackend":false,"DeviceTypeID":1,"PatientID":4515697,"TokenID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","PatientTypeID":1,"PatientType":1,"Latitude":37.785834,"Longitude":-122.406417}
// flutter: response.body:
// flutter: {"Date":null,"LanguageID":0,"ServiceName":0,"Time":null,"AndroidLink":null,"AuthenticationTokenID":null,"Data":null,"Dataw":false,"DietType":0,"DietTypeID":0,"ErrorCode":null,"ErrorEndUserMessage":null,"ErrorEndUserMessageN":null,"ErrorMessage":null,"ErrorStatusCode":0,"ErrorType":0,"FoodCategory":0,"IOSLink":null,"IsAuthenticated":false,"MealOrderStatus":0,"MealType":0,"MessageStatus":1,"NumberOfResultRecords":0,"PatientBlodType":null,"SuccessMsg":null,"SuccessMsgN":null,"VidaUpdatedResponse":null,"IsHMGPatient":false,"LogInTokenID":null,"PhysicalActivityData":null,"RowExists":0,"UserDetailData":null,"UserDetailData_New":null,"UserProgressForMonthData":null,"UserProgressForTodayData":[{"QuantityConsumed":0.00,"PercentageConsumed":0.00,"PercentageLeft":100.00,"QuantityLimit":2125.00}],"UserProgressForWeekData":null,"UserProgressHistoryData":[],"VerificationCode":null,"isSMSSent":false}
// Progress":1 means monthly data
// flutter: URL : https://hmgwebservices.com/Services/H2ORemainder.svc/REST/H2O_GetUserProgress
// flutter: {"Progress":3,"MobileNumber":"504278212","IdentificationNo":"2530976584","VersionID":20.0,"Channel":3,"LanguageID":2,"IPAdress":"10.20.10.20","generalid":"Cs2020@2016$2958","PatientOutSA":0,"SessionID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","isDentalAllowedBackend":false,"DeviceTypeID":1,"PatientID":4515697,"TokenID":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlblR5cGUiOjIsIm9iaiI6eyJUaW1lIjoiMjAyNS0xMi0yOFQwODozNjo0My45MTY0MzRaIiwiUGF0aWVudElEIjoiNDUxNTY5NyIsIlBhdGllbnRNZXJnZWRJRHMiOiI0NTE1Njk3IiwiUGF0aWVudE91dFNBIjpmYWxzZX0sImV4cCI6MTc2Njk2NjQwMH0.l1rCcs2OAU5u2J-iZMiO7NX6shGzLJV0hlYtVh2JeqY","PatientTypeID":1,"PatientType":1,"Latitude":37.785834,"Longitude":-122.406417}
// flutter: response.body:
// [log] {"Date":null,"LanguageID":0,"ServiceName":0,"Time":null,"AndroidLink":null,"AuthenticationTokenID":null,"Data":null,"Dataw":false,"DietType":0,"DietTypeID":0,"ErrorCode":null,"ErrorEndUserMessage":null,"ErrorEndUserMessageN":null,"ErrorMessage":null,"ErrorStatusCode":0,"ErrorType":0,"FoodCategory":0,"IOSLink":null,"IsAuthenticated":false,"MealOrderStatus":0,"MealType":0,"MessageStatus":1,"NumberOfResultRecords":0,"PatientBlodType":null,"SuccessMsg":null,"SuccessMsgN":null,"VidaUpdatedResponse":null,"IsHMGPatient":false,"LogInTokenID":null,"PhysicalActivityData":null,"RowExists":0,"UserDetailData":null,"UserDetailData_New":null,"UserProgressForMonthData":[{"MonthNumber":1,"MonthName":"January","PercentageConsumed":0},{"MonthNumber":2,"MonthName":"February","PercentageConsumed":0},{"MonthNumber":3,"MonthName":"March","PercentageConsumed":0},{"MonthNumber":4,"MonthName":"April","PercentageConsumed":0},{"MonthNumber":5,"MonthName":"May","PercentageConsumed":0},{"MonthNumber":6,"MonthName":"June","PercentageConsumed":0},{"MonthNumber":7,"MonthName":"July","PercentageConsumed":0},{"MonthNumber":8,"MonthName":"August","PercentageConsumed":0},{"MonthNumber":9,"MonthName":"September","PercentageConsumed":0},{"MonthNumber":10,"MonthName":"October","PercentageConsumed":0},{"MonthNumber":11,"MonthName":"November","PercentageConsumed":0},{"MonthNumber":12,"MonthName":"December","PercentageConsumed":0}],"UserProgressForTodayData":null,"UserProgressForWeekData":null,"UserProgressHistoryData":[],"VerificationCode":null,"isSMSSent":false}
class AppUrls {
static final hmgTermsUrlEn = 'https://hmg.com/en/Pages/Terms.aspx';
static final hmgPrivacyUrlEn = 'https://hmg.com/en/Pages/Privacy.aspx';
static final hmgTermsUrlAr = 'https://hmg.com/ar/Pages/Terms.aspx';
static final hmgPrivacyUrlAr = 'https://hmg.com/ar/Pages/Privacy.aspx';
}

@ -127,6 +127,7 @@ extension EmailValidator on String {
Widget toText12(
{bool isEnglishOnly = false,
Color? color,
Color? decorationColor,
bool isUnderLine = false,
TextAlign textAlignment = TextAlign.start,
bool isBold = false,
@ -147,7 +148,7 @@ extension EmailValidator on String {
color: color ?? AppColors.blackColor,
letterSpacing: letterSpacing ?? 0,
height: height,
decorationColor: isUnderLine ? AppColors.blackColor : null,
decorationColor: decorationColor ?? AppColors.blackColor,
decoration: isUnderLine ? TextDecoration.underline : null,
fontFamily: getIt.get<AppState>().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins',
),

@ -0,0 +1,147 @@
class ExplanationsResponseModel {
final ExplanationsDataDetails? dataDetails;
ExplanationsResponseModel({this.dataDetails});
factory ExplanationsResponseModel.fromJson(Map<String, dynamic> json) {
return ExplanationsResponseModel(
dataDetails: json['dataDetails'] != null
? ExplanationsDataDetails.fromJson(json['dataDetails'])
: null,
);
}
Map<String, dynamic> toJson() {
return {
'dataDetails': dataDetails?.toJson(),
};
}
}
class ExplanationsDataDetails {
final List<ExplanationEvidence>? supportingEvidence;
final List<ExplanationEvidence>? conflictingEvidence;
final List<ExplanationEvidence>? unconfirmedEvidence;
final String? message;
final List<String>? errorList;
final int? id;
final String? language;
final String? generalId;
final String? createDate;
final String? lastEditDate;
final String? createdBy;
final String? lastEditBy;
final bool? active;
final int? sortOrder;
final int? userType;
final String? userId;
ExplanationsDataDetails({
this.supportingEvidence,
this.conflictingEvidence,
this.unconfirmedEvidence,
this.message,
this.errorList,
this.id,
this.language,
this.generalId,
this.createDate,
this.lastEditDate,
this.createdBy,
this.lastEditBy,
this.active,
this.sortOrder,
this.userType,
this.userId,
});
factory ExplanationsDataDetails.fromJson(Map<String, dynamic> json) {
return ExplanationsDataDetails(
supportingEvidence: json['supporting_evidence'] != null
? (json['supporting_evidence'] as List)
.map((item) => ExplanationEvidence.fromJson(item))
.toList()
: null,
conflictingEvidence: json['conflicting_evidence'] != null
? (json['conflicting_evidence'] as List)
.map((item) => ExplanationEvidence.fromJson(item))
.toList()
: null,
unconfirmedEvidence: json['unconfirmed_evidence'] != null
? (json['unconfirmed_evidence'] as List)
.map((item) => ExplanationEvidence.fromJson(item))
.toList()
: null,
message: json['Message'],
errorList: json['ErrorList'] != null
? List<String>.from(json['ErrorList'])
: null,
id: json['Id'],
language: json['language'],
generalId: json['generalId'],
createDate: json['CreateDate'],
lastEditDate: json['LastEditDate'],
createdBy: json['CreatedBy'],
lastEditBy: json['LastEditBy'],
active: json['Active'],
sortOrder: json['SortOrder'],
userType: json['userType'],
userId: json['userId'],
);
}
Map<String, dynamic> toJson() {
return {
'supporting_evidence': supportingEvidence?.map((item) => item.toJson()).toList(),
'conflicting_evidence': conflictingEvidence?.map((item) => item.toJson()).toList(),
'unconfirmed_evidence': unconfirmedEvidence?.map((item) => item.toJson()).toList(),
'Message': message,
'ErrorList': errorList,
'Id': id,
'language': language,
'generalId': generalId,
'CreateDate': createDate,
'LastEditDate': lastEditDate,
'CreatedBy': createdBy,
'LastEditBy': lastEditBy,
'Active': active,
'SortOrder': sortOrder,
'userType': userType,
'userId': userId,
};
}
}
class ExplanationEvidence {
final String? id;
final String? name;
final String? commonName;
ExplanationEvidence({
this.id,
this.name,
this.commonName,
});
factory ExplanationEvidence.fromJson(Map<String, dynamic> json) {
return ExplanationEvidence(
id: json['id'],
name: json['name'],
commonName: json['common_name'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'common_name': commonName,
};
}
// Helper method to get display name
String getDisplayName() {
return commonName ?? name ?? '';
}
}

@ -0,0 +1,176 @@
class RationaleResponseModel {
final RationaleDataDetails? dataDetails;
RationaleResponseModel({this.dataDetails});
factory RationaleResponseModel.fromJson(Map<String, dynamic> json) {
return RationaleResponseModel(
dataDetails: json['dataDetails'] != null
? RationaleDataDetails.fromJson(json['dataDetails'])
: null,
);
}
Map<String, dynamic> toJson() {
return {
'dataDetails': dataDetails?.toJson(),
};
}
}
class RationaleDataDetails {
final String? type;
final List<RationaleObservationParam>? observationParams;
final List<RationaleConditionParam>? conditionParams;
final String? message;
final List<String>? errorList;
final int? id;
final String? language;
final String? generalId;
final String? createDate;
final String? lastEditDate;
final String? createdBy;
final String? lastEditBy;
final bool? active;
final int? sortOrder;
final int? userType;
final String? userId;
RationaleDataDetails({
this.type,
this.observationParams,
this.conditionParams,
this.message,
this.errorList,
this.id,
this.language,
this.generalId,
this.createDate,
this.lastEditDate,
this.createdBy,
this.lastEditBy,
this.active,
this.sortOrder,
this.userType,
this.userId,
});
factory RationaleDataDetails.fromJson(Map<String, dynamic> json) {
return RationaleDataDetails(
type: json['type'],
observationParams: json['observation_params'] != null
? (json['observation_params'] as List)
.map((item) => RationaleObservationParam.fromJson(item))
.toList()
: null,
conditionParams: json['condition_params'] != null
? (json['condition_params'] as List)
.map((item) => RationaleConditionParam.fromJson(item))
.toList()
: null,
message: json['Message'],
errorList: json['ErrorList'] != null
? List<String>.from(json['ErrorList'])
: null,
id: json['Id'],
language: json['language'],
generalId: json['generalId'],
createDate: json['CreateDate'],
lastEditDate: json['LastEditDate'],
createdBy: json['CreatedBy'],
lastEditBy: json['LastEditBy'],
active: json['Active'],
sortOrder: json['SortOrder'],
userType: json['userType'],
userId: json['userId'],
);
}
Map<String, dynamic> toJson() {
return {
'type': type,
'observation_params': observationParams?.map((item) => item.toJson()).toList(),
'condition_params': conditionParams?.map((item) => item.toJson()).toList(),
'Message': message,
'ErrorList': errorList,
'Id': id,
'language': language,
'generalId': generalId,
'CreateDate': createDate,
'LastEditDate': lastEditDate,
'CreatedBy': createdBy,
'LastEditBy': lastEditBy,
'Active': active,
'SortOrder': sortOrder,
'userType': userType,
'userId': userId,
};
}
}
class RationaleObservationParam {
final String? id;
final String? name;
final String? commonName;
RationaleObservationParam({
this.id,
this.name,
this.commonName,
});
factory RationaleObservationParam.fromJson(Map<String, dynamic> json) {
return RationaleObservationParam(
id: json['id'],
name: json['name'],
commonName: json['common_name'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'common_name': commonName,
};
}
// Helper method to get display name
String getDisplayName() {
return commonName ?? name ?? '';
}
}
class RationaleConditionParam {
final String? id;
final String? name;
final String? commonName;
RationaleConditionParam({
this.id,
this.name,
this.commonName,
});
factory RationaleConditionParam.fromJson(Map<String, dynamic> json) {
return RationaleConditionParam(
id: json['id'],
name: json['name'],
commonName: json['common_name'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'common_name': commonName,
};
}
// Helper method to get display name
String getDisplayName() {
return commonName ?? name ?? '';
}
}

@ -0,0 +1,151 @@
class TriageLevelResponseModel {
final TriageLevelDataDetails? dataDetails;
TriageLevelResponseModel({this.dataDetails});
factory TriageLevelResponseModel.fromJson(Map<String, dynamic> json) {
return TriageLevelResponseModel(
dataDetails: json['dataDetails'] != null
? TriageLevelDataDetails.fromJson(json['dataDetails'])
: null,
);
}
Map<String, dynamic> toJson() {
return {
'dataDetails': dataDetails?.toJson(),
};
}
}
class TriageLevelDataDetails {
final String? triageLevel;
final List<SeriousEvidence>? serious;
final String? rootCause;
final bool? teleconsultationApplicable;
final String? message;
final List<String>? errorList;
final int? id;
final String? language;
final String? generalId;
final String? createDate;
final String? lastEditDate;
final String? createdBy;
final String? lastEditBy;
final bool? active;
final int? sortOrder;
final int? userType;
final String? userId;
TriageLevelDataDetails({
this.triageLevel,
this.serious,
this.rootCause,
this.teleconsultationApplicable,
this.message,
this.errorList,
this.id,
this.language,
this.generalId,
this.createDate,
this.lastEditDate,
this.createdBy,
this.lastEditBy,
this.active,
this.sortOrder,
this.userType,
this.userId,
});
factory TriageLevelDataDetails.fromJson(Map<String, dynamic> json) {
return TriageLevelDataDetails(
triageLevel: json['triage_level'],
serious: json['serious'] != null
? (json['serious'] as List)
.map((item) => SeriousEvidence.fromJson(item))
.toList()
: null,
rootCause: json['root_cause'],
teleconsultationApplicable: json['teleconsultation_applicable'],
message: json['Message'],
errorList: json['ErrorList'] != null
? List<String>.from(json['ErrorList'])
: null,
id: json['Id'],
language: json['language'],
generalId: json['generalId'],
createDate: json['CreateDate'],
lastEditDate: json['LastEditDate'],
createdBy: json['CreatedBy'],
lastEditBy: json['LastEditBy'],
active: json['Active'],
sortOrder: json['SortOrder'],
userType: json['userType'],
userId: json['userId'],
);
}
Map<String, dynamic> toJson() {
return {
'triage_level': triageLevel,
'serious': serious?.map((item) => item.toJson()).toList(),
'root_cause': rootCause,
'teleconsultation_applicable': teleconsultationApplicable,
'Message': message,
'ErrorList': errorList,
'Id': id,
'language': language,
'generalId': generalId,
'CreateDate': createDate,
'LastEditDate': lastEditDate,
'CreatedBy': createdBy,
'LastEditBy': lastEditBy,
'Active': active,
'SortOrder': sortOrder,
'userType': userType,
'userId': userId,
};
}
}
class SeriousEvidence {
final String? id;
final String? name;
final String? commonName;
final String? seriousness;
final bool? isEmergency;
SeriousEvidence({
this.id,
this.name,
this.commonName,
this.seriousness,
this.isEmergency,
});
factory SeriousEvidence.fromJson(Map<String, dynamic> json) {
return SeriousEvidence(
id: json['id'],
name: json['name'],
commonName: json['common_name'],
seriousness: json['seriousness'],
isEmergency: json['is_emergency'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'common_name': commonName,
'seriousness': seriousness,
'is_emergency': isEmergency,
};
}
// Helper method to get display name
String getDisplayName() {
return commonName ?? name ?? '';
}
}

@ -91,18 +91,34 @@ class TriageDataDetails {
class TriageQuestion {
final int? type;
final String? text;
final String? explication;
final List<String>? instruction;
final List<TriageQuestionItem>? items;
TriageQuestion({
this.type,
this.text,
this.explication,
this.instruction,
this.items,
});
factory TriageQuestion.fromJson(Map<String, dynamic> json) {
// TESTING: Add dummy instructions to every question for testing UI
final dummyInstructions = [
"Clean the thermometer using soap and cold water.",
"For rectal insertion, apply a small amount of petroleum jelly to the end of the thermometer.",
"Insert the silver tip into the anus or mouth.",
"Hold the thermometer in place for 2 minutes, or as long as recommended by the manufacturer.",
];
final dummyExplication = "The most accurate measurement is rectal temperature. You can also measure the temperature under the armpit or use contactless thermometers. However, using the contact thermometer is more accurate. Follow the thermometer manufacturer's instructions. A temperature above 37°C or 98.6°F is classified as a fever.";
return TriageQuestion(
type: json['type'],
text: json['text'],
explication: json['explication'] ?? dummyExplication, // Use dummy if null
instruction: json['instruction'] != null ? List<String>.from(json['instruction']) : dummyInstructions, // Use dummy if null
items: json['items'] != null ? (json['items'] as List).map((item) => TriageQuestionItem.fromJson(item)).toList() : null,
);
}
@ -111,9 +127,18 @@ class TriageQuestion {
return {
'type': type,
'text': text,
'explication': explication,
'instruction': instruction,
'items': items?.map((item) => item.toJson()).toList(),
};
}
/// Check if instruction OR explication is available
bool get hasInstructions {
final hasInstruction = instruction != null && instruction!.isNotEmpty;
final hasExplication = explication != null && explication!.isNotEmpty;
return hasInstruction || hasExplication;
}
}
class TriageQuestionItem {

@ -8,10 +8,13 @@ import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/req_models/schedule_appointment_request_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/explanations_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/rationale_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/schedule_appointment_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_level_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
@ -40,7 +43,7 @@ abstract class SymptomsCheckerRepo {
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>> getSuggestions({
required int age,
required int gender,
required List<String> evidenceIds,
required List<Map<String, dynamic>> evidenceList,
required String language,
required String userSessionToken,
required String sessionId,
@ -49,7 +52,7 @@ abstract class SymptomsCheckerRepo {
Future<Either<Failure, GenericApiModel<TriageDataDetails>>> getDiagnosisForTriage({
required int age,
required int gender,
required List<String> evidenceIds,
required List<Map<String, dynamic>> initialEvidenceList,
List<Map<String, String>>? triageEvidence,
required String language,
required String userSessionToken,
@ -66,6 +69,34 @@ abstract class SymptomsCheckerRepo {
required ScheduleAppointmentRequestModel request,
required String userSessionToken,
});
Future<Either<Failure, GenericApiModel<RationaleResponseModel>>> getRationale({
required int age,
required int gender,
required List<Map<String, dynamic>> evidence,
required String language,
required String generalId,
required String userSessionToken,
});
Future<Either<Failure, GenericApiModel<TriageLevelResponseModel>>> getTriage({
required int age,
required int gender,
required List<Map<String, dynamic>> evidence,
required String language,
required String generalId,
required String userSessionToken,
});
Future<Either<Failure, GenericApiModel<ExplanationsResponseModel>>> getExplanations({
required int age,
required int gender,
required List<Map<String, dynamic>> evidence,
required String language,
required String target,
required String generalId,
required String userSessionToken,
});
}
class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
@ -201,12 +232,20 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
required String userSessionToken,
required String sessionId,
}) async {
// Build evidence list with "initial" source for symptoms
final List<Map<String, dynamic>> evidenceList = evidenceIds.map((id) {
return {
"id": id,
"source": "initial",
};
}).toList();
final Map<String, dynamic> body = {
"age": {
"value": age,
},
"gender": gender,
"evidence": evidenceIds.map((id) => {"id": id}).toList(),
"evidence": evidenceList,
"language": language,
"generalId": sessionId,
};
@ -265,21 +304,19 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
Future<Either<Failure, GenericApiModel<TriageDataDetails>>> getDiagnosisForTriage({
required int age,
required int gender,
required List<String> evidenceIds,
required List<Map<String, dynamic>> initialEvidenceList,
List<Map<String, String>>? triageEvidence, // Additional triage-specific evidence
required String language,
required String userSessionToken,
required String sessionId,
}) async {
// Build evidence list: combine initial symptoms with triage evidence
// Build evidence list: combine initial evidence (with sources) with triage evidence
List<Map<String, dynamic>> evidenceList = [];
// Add initial evidence as simple IDs
for (var id in evidenceIds) {
evidenceList.add({"id": id});
}
// Add initial evidence (already has sources: initial, red_flags, suggest)
evidenceList.addAll(initialEvidenceList);
// Add triage evidence with proper format (id, choice_id, source)
// Add triage evidence with proper format (id, choice_id, source=triage)
if (triageEvidence != null && triageEvidence.isNotEmpty) {
evidenceList.addAll(triageEvidence);
}
@ -351,7 +388,7 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>> getSuggestions({
required int age,
required int gender,
required List<String> evidenceIds,
required List<Map<String, dynamic>> evidenceList,
required String language,
required String userSessionToken,
required String sessionId,
@ -361,7 +398,7 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
"value": age,
},
"gender": gender,
"evidence": evidenceIds.map((id) => {"id": id}).toList(),
"evidence": evidenceList,
"language": language,
"generalId": sessionId,
};
@ -529,4 +566,210 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<RationaleResponseModel>>> getRationale({
required int age,
required int gender,
required List<Map<String, dynamic>> evidence,
required String language,
required String generalId,
required String userSessionToken,
}) async {
final Map<String, dynamic> body = {
"age": {
"value": age,
},
"gender": gender,
"evidence": evidence,
"language": language,
"generalId": generalId,
};
Map<String, String> headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer $userSessionToken',
};
try {
GenericApiModel<RationaleResponseModel>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getRationale,
apiHeaders: headers,
body: body,
isExternal: true,
isAllowAny: true,
onFailure: (error, statusCode, {messageStatus, failureType}) {
loggerService.logError("GetRationale API Failed: $error");
failure = failureType ?? ServerFailure(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final Map<String, dynamic> responseData = response is String ? jsonDecode(response) : response;
RationaleResponseModel rationaleResponse = RationaleResponseModel.fromJson(responseData);
apiResponse = GenericApiModel<RationaleResponseModel>(
messageStatus: messageStatus ?? 1,
statusCode: statusCode,
errorMessage: errorMessage,
data: rationaleResponse,
);
} catch (e, stackTrace) {
loggerService.logError("Error parsing GetRationale response: $e");
loggerService.logError("StackTrace: $stackTrace");
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e, stackTrace) {
loggerService.logError("Exception in getRationale: $e");
loggerService.logError("StackTrace: $stackTrace");
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<TriageLevelResponseModel>>> getTriage({
required int age,
required int gender,
required List<Map<String, dynamic>> evidence,
required String language,
required String generalId,
required String userSessionToken,
}) async {
final Map<String, dynamic> body = {
"age": {
"value": age,
},
"gender": gender,
"evidence": evidence,
"language": language,
"generalId": generalId,
};
Map<String, String> headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer $userSessionToken',
};
try {
GenericApiModel<TriageLevelResponseModel>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getTriage,
apiHeaders: headers,
body: body,
isExternal: true,
isAllowAny: true,
onFailure: (error, statusCode, {messageStatus, failureType}) {
loggerService.logError("GetTriage API Failed: $error");
failure = failureType ?? ServerFailure(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final Map<String, dynamic> responseData = response is String ? jsonDecode(response) : response;
TriageLevelResponseModel triageResponse = TriageLevelResponseModel.fromJson(responseData);
apiResponse = GenericApiModel<TriageLevelResponseModel>(
messageStatus: messageStatus ?? 1,
statusCode: statusCode,
errorMessage: errorMessage,
data: triageResponse,
);
} catch (e, stackTrace) {
loggerService.logError("Error parsing GetTriage response: $e");
loggerService.logError("StackTrace: $stackTrace");
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e, stackTrace) {
loggerService.logError("Exception in getTriage: $e");
loggerService.logError("StackTrace: $stackTrace");
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<ExplanationsResponseModel>>> getExplanations({
required int age,
required int gender,
required List<Map<String, dynamic>> evidence,
required String language,
required String target,
required String generalId,
required String userSessionToken,
}) async {
final Map<String, dynamic> body = {
"age": {
"value": age,
},
"gender": gender,
"evidence": evidence,
"language": language,
"target": target,
"generalId": generalId,
};
Map<String, String> headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer $userSessionToken',
};
try {
GenericApiModel<ExplanationsResponseModel>? apiResponse;
Failure? failure;
await apiClient.post(
ApiConsts.getExplanations,
apiHeaders: headers,
body: body,
isExternal: true,
isAllowAny: true,
onFailure: (error, statusCode, {messageStatus, failureType}) {
loggerService.logError("GetExplanations API Failed: $error");
failure = failureType ?? ServerFailure(error);
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final Map<String, dynamic> responseData = response is String ? jsonDecode(response) : response;
ExplanationsResponseModel explanationsResponse = ExplanationsResponseModel.fromJson(responseData);
apiResponse = GenericApiModel<ExplanationsResponseModel>(
messageStatus: messageStatus ?? 1,
statusCode: statusCode,
errorMessage: errorMessage,
data: explanationsResponse,
);
} catch (e, stackTrace) {
loggerService.logError("Error parsing GetExplanations response: $e");
loggerService.logError("StackTrace: $stackTrace");
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e, stackTrace) {
loggerService.logError("Exception in getExplanations: $e");
loggerService.logError("StackTrace: $stackTrace");
return Left(UnknownFailure(e.toString()));
}
}
}

@ -8,10 +8,13 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/data/organ_mapping
import 'package:hmg_patient_app_new/features/symptoms_checker/models/organ_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/req_models/schedule_appointment_request_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/explanations_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/rationale_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/schedule_appointment_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_level_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_repo.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
@ -66,6 +69,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
bool isSuggestionsLoading = false;
bool isTriageDiagnosisLoading = false;
bool isScheduleAppointmentLoading = false;
bool isRationaleLoading = false;
bool isTriageLevelLoading = false;
bool isExplanationsLoading = false;
// Flag to track if appointment is being booked via symptoms checker flow
bool isBookingFromSymptomsChecker = false;
@ -76,6 +82,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
RiskAndSuggestionsResponseModel? riskFactorsResponse;
RiskAndSuggestionsResponseModel? suggestionsResponse;
TriageDataDetails? triageDataDetails;
RationaleResponseModel? rationaleResponse;
TriageLevelResponseModel? triageLevelResponse;
ExplanationsResponseModel? explanationsResponse;
// Triage state
int? _selectedTriageChoiceIndex; // Deprecated - keeping for backward compatibility
@ -87,6 +96,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
String? _selectedSingleItemId; // Store which item was selected
int? _selectedSingleChoiceIndex; // Store which choice was selected
// Triage question history tracking
final List<TriageDataDetails> _triageQuestionHistory = []; // Stack of previous questions
final List<List<Map<String, String>>> _triageEvidenceHistory = []; // Stack of evidence states at each question
// Selected risk factors tracking
final Set<String> _selectedRiskFactorIds = {};
@ -178,6 +191,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
/// Get the number of triage questions answered
int get triageQuestionCount => _triageQuestionCount;
/// Check if this is the first triage question (no history)
bool get isFirstTriageQuestion => _triageQuestionHistory.isEmpty;
List<GetClinicDetailsResponseModel> clinicDetailsList = [];
/// Get choice index for a specific item
@ -745,6 +761,44 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
return evidenceIds;
}
/// Get all evidence with their sources based on selection step
/// Source values:
/// - "initial" for symptoms (used in GetRiskFactors)
/// - "red_flags" for risk factors (used in GetSuggestions)
/// - "suggest" for suggestions (used in GetDiagnosis)
List<Map<String, dynamic>> getAllEvidenceWithSources({String defaultSource = "initial"}) {
List<Map<String, dynamic>> evidenceList = [];
// Add selected symptoms with source "initial"
final selectedSymptoms = getAllSelectedSymptoms();
for (var symptom in selectedSymptoms.where((s) => s.id != null)) {
evidenceList.add({
"id": symptom.id!,
"source": "initial",
});
}
// Add selected risk factors with source "red_flags" (excluding "not_applicable")
final selectedRiskFactors = getAllSelectedRiskFactors();
for (var riskFactor in selectedRiskFactors.where((rf) => rf.id != null && rf.id != "not_applicable")) {
evidenceList.add({
"id": riskFactor.id!,
"source": "red_flags",
});
}
// Add selected suggestions with source "suggest" (excluding "not_applicable")
final selectedSuggestions = getAllSelectedSuggestions();
for (var suggestion in selectedSuggestions.where((s) => s.id != null && s.id != "not_applicable")) {
evidenceList.add({
"id": suggestion.id!,
"source": "suggest",
});
}
return evidenceList;
}
/// Fetch risk factors based on selected symptoms
Future<void> fetchSuggestions({
Function()? onSuccess,
@ -768,21 +822,30 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
return;
}
// Extract symptom IDs
List<String> evidenceIds = selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList();
// Build evidence list with proper sources
List<Map<String, dynamic>> evidenceList = [];
// Get all selected symptoms
final selectedRisks = getAllSelectedRiskFactors();
// Add selected symptoms with source "initial"
for (var symptom in selectedSymptoms.where((s) => s.id != null)) {
evidenceList.add({
"id": symptom.id!,
"source": "initial",
});
}
if (selectedRisks.isNotEmpty) {
List<String> evidenceRisksIds = selectedRisks.where((s) => s.id != null && s.id != "not_applicable").map((s) => s.id!).toList();
evidenceIds.addAll(evidenceRisksIds);
// Get selected risk factors and add with source "red_flags"
final selectedRisks = getAllSelectedRiskFactors();
for (var riskFactor in selectedRisks.where((rf) => rf.id != null && rf.id != "not_applicable")) {
evidenceList.add({
"id": riskFactor.id!,
"source": "red_flags",
});
}
await getSuggestions(
age: _selectedAge!,
gender: _getGenderAsInt(_selectedGender),
evidenceIds: evidenceIds,
evidenceList: evidenceList,
language: appState.isArabic() ? 'ar' : 'en',
onSuccess: (response) {
if (onSuccess != null) {
@ -801,7 +864,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
Future<void> getSuggestions({
required int age,
required int gender,
required List<String> evidenceIds,
required List<Map<String, dynamic>> evidenceList,
required String language,
Function(RiskAndSuggestionsResponseModel)? onSuccess,
Function(String)? onError,
@ -812,7 +875,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
final result = await symptomsCheckerRepo.getSuggestions(
age: age,
gender: gender,
evidenceIds: evidenceIds,
evidenceList: evidenceList,
language: language,
sessionId: currentSessionId,
userSessionToken: currentSessionAuthToken,
@ -863,19 +926,27 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
Future<void> getDiagnosisForTriage({
required int age,
required int gender,
required List<String> evidenceIds,
List<Map<String, String>>? triageEvidence,
required List<Map<String, dynamic>> initialEvidenceList, // Evidence with sources (initial, red_flags, suggest)
List<Map<String, String>>? triageEvidence, // Triage-specific evidence (id, choice_id, source=triage)
required String language,
Function(TriageDataDetails)? onSuccess,
Function(String)? onError,
bool isGoingBack = false, // Flag to indicate if we're going back in history
}) async {
// Save current question and evidence to history BEFORE making new API call
// (but only if we're moving forward, not going back)
if (!isGoingBack && triageDataDetails != null) {
_triageQuestionHistory.add(triageDataDetails!);
_triageEvidenceHistory.add(List<Map<String, String>>.from(_triageEvidenceList));
}
isTriageDiagnosisLoading = true;
notifyListeners();
final result = await symptomsCheckerRepo.getDiagnosisForTriage(
age: age,
gender: gender,
evidenceIds: evidenceIds,
initialEvidenceList: initialEvidenceList,
triageEvidence: triageEvidence,
language: language,
sessionId: currentSessionId,
@ -923,10 +994,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
return;
}
// Get all evidence IDs
final evidenceIds = getAllEvidenceIds();
// Get all evidence with sources
final evidenceList = getAllEvidenceWithSources();
if (evidenceIds.isEmpty) {
if (evidenceList.isEmpty) {
if (onError != null) {
onError('No evidence selected');
}
@ -936,7 +1007,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
await getDiagnosisForTriage(
age: _selectedAge!,
gender: _getGenderAsInt(_selectedGender),
evidenceIds: evidenceIds,
initialEvidenceList: evidenceList,
language: appState.isArabic() ? 'ar' : 'en',
onSuccess: (response) {
if (onSuccess != null) {
@ -993,7 +1064,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
_triageEvidenceList.add({
"id": itemId,
"choice_id": choiceId,
"source": "triage",
"source": "suggest",
});
notifyListeners();
}
@ -1009,6 +1080,39 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
notifyListeners();
}
/// Go back to previous triage question
/// Returns true if successfully went back, false if already at first question
bool goBackToPreviousTriageQuestion() {
if (_triageQuestionHistory.isEmpty) {
return false; // Already at first question
}
// Pop the last question from history
final previousQuestion = _triageQuestionHistory.removeLast();
final previousEvidence = _triageEvidenceHistory.removeLast();
// Restore the previous question
triageDataDetails = previousQuestion;
// Restore the previous evidence state
_triageEvidenceList.clear();
_triageEvidenceList.addAll(previousEvidence);
// Decrement question count
if (_triageQuestionCount > 0) {
_triageQuestionCount--;
}
// Clear current selections
_selectedTriageChoiceIndex = null;
_selectedTriageChoicesByItemId.clear();
_selectedSingleItemId = null;
_selectedSingleChoiceIndex = null;
notifyListeners();
return true;
}
void reset() {
_currentView = BodyView.front;
_selectedOrganIds.clear();
@ -1018,6 +1122,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
_triageEvidenceList.clear();
_selectedTriageChoicesByItemId.clear();
_triageQuestionCount = 0; // Reset question count
_triageQuestionHistory.clear(); // Clear question history
_triageEvidenceHistory.clear(); // Clear evidence history
_currentZoomScale = 1.0; // Reset zoom scale
_symptomSearchQuery = ''; // Reset search query
_filteredOrganSymptomsResults.clear(); // Clear filtered results
@ -1054,6 +1160,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
_selectedRiskFactorIds.clear();
_selectedSuggestionsIds.clear();
_triageEvidenceList.clear();
_triageQuestionHistory.clear(); // Clear question history
_triageEvidenceHistory.clear(); // Clear evidence history
_selectedTriageChoicesByItemId.clear();
_selectedSingleItemId = null;
_selectedSingleChoiceIndex = null;
@ -1066,6 +1174,28 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
notifyListeners();
}
/// Clear selections when reaching possible conditions screen
/// This clears organs, symptoms, risk factors, suggestions, and questions
void clearSelectionsForPossibleConditions() {
_selectedOrganIds.clear();
_selectedSymptomsByOrgan.clear();
_selectedRiskFactorIds.clear();
_selectedSuggestionsIds.clear();
_triageEvidenceList.clear();
_triageQuestionHistory.clear();
_triageEvidenceHistory.clear();
_selectedTriageChoicesByItemId.clear();
_selectedSingleItemId = null;
_selectedSingleChoiceIndex = null;
_triageQuestionCount = 0;
_symptomSearchQuery = '';
_filteredOrganSymptomsResults.clear();
_isBottomSheetExpanded = false;
_tooltipTimer?.cancel();
_tooltipOrganId = null;
notifyListeners();
}
// User Info Flow Methods
/// Set current page in user info flow
@ -1170,6 +1300,34 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
};
}
// User Consent Methods
bool isTermsAccepted = false;
bool isPrivacyAccepted = false;
/// Toggle terms acceptance
void toggleTermsAcceptance() {
isTermsAccepted = !isTermsAccepted;
notifyListeners();
}
/// Toggle privacy policy acceptance
void togglePrivacyAcceptance() {
isPrivacyAccepted = !isPrivacyAccepted;
notifyListeners();
}
void setTermsAcceptance(bool value) {
isTermsAccepted = value;
notifyListeners();
}
/// Toggle privacy policy acceptance
void setPrivacyAcceptance(bool value) {
isPrivacyAccepted = value;
notifyListeners();
}
Future<void> getSymptomsUserDetails({
required String userName,
required String password,
@ -1352,6 +1510,170 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
);
}
/// Get Rationale for symptoms
/// This API shows which symptoms and conditions are being considered
Future<void> fetchRationale({
required List<Map<String, dynamic>> evidence,
Function(RationaleResponseModel)? onSuccess,
Function(String)? onError,
}) async {
// Validate user info
if (_selectedAge == null || _selectedGender == null) {
if (onError != null) {
onError('User information is incomplete');
}
return;
}
isRationaleLoading = true;
notifyListeners();
final result = await symptomsCheckerRepo.getRationale(
age: _selectedAge!,
gender: _getGenderAsInt(_selectedGender),
evidence: evidence,
language: appState.isArabic() ? 'ar' : 'en',
generalId: currentSessionId,
userSessionToken: currentSessionAuthToken,
);
result.fold(
(failure) async {
isRationaleLoading = false;
notifyListeners();
await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.toString());
}
},
(apiResponse) {
isRationaleLoading = false;
if (apiResponse.messageStatus == 1 && apiResponse.data != null) {
rationaleResponse = apiResponse.data;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse.data!);
}
} else {
notifyListeners();
if (onError != null) {
onError(apiResponse.errorMessage ?? 'Failed to fetch rationale');
}
}
},
);
}
/// Get Triage Level for symptoms
/// This API determines the urgency level and serious symptoms
Future<void> fetchTriageLevel({
required List<Map<String, dynamic>> evidence,
Function(TriageLevelResponseModel)? onSuccess,
Function(String)? onError,
}) async {
// Validate user info
if (_selectedAge == null || _selectedGender == null) {
if (onError != null) {
onError('User information is incomplete');
}
return;
}
isTriageLevelLoading = true;
notifyListeners();
final result = await symptomsCheckerRepo.getTriage(
age: _selectedAge!,
gender: _getGenderAsInt(_selectedGender),
evidence: evidence,
language: appState.isArabic() ? 'ar' : 'en',
generalId: currentSessionId,
userSessionToken: currentSessionAuthToken,
);
result.fold(
(failure) async {
isTriageLevelLoading = false;
notifyListeners();
await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.toString());
}
},
(apiResponse) {
isTriageLevelLoading = false;
if (apiResponse.messageStatus == 1 && apiResponse.data != null) {
triageLevelResponse = apiResponse.data;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse.data!);
}
} else {
notifyListeners();
if (onError != null) {
onError(apiResponse.errorMessage ?? 'Failed to fetch triage level');
}
}
},
);
}
/// Get Explanations for a specific condition
/// This API explains why a condition was suggested based on evidence
Future<void> fetchExplanations({
required String targetConditionId,
required List<Map<String, dynamic>> evidence,
Function(ExplanationsResponseModel)? onSuccess,
Function(String)? onError,
}) async {
// Validate user info
if (_selectedAge == null || _selectedGender == null) {
if (onError != null) {
onError('User information is incomplete');
}
return;
}
isExplanationsLoading = true;
notifyListeners();
final result = await symptomsCheckerRepo.getExplanations(
age: _selectedAge!,
gender: _getGenderAsInt(_selectedGender),
evidence: evidence,
language: appState.isArabic() ? 'ar' : 'en',
target: targetConditionId,
generalId: currentSessionId,
userSessionToken: currentSessionAuthToken,
);
result.fold(
(failure) async {
isExplanationsLoading = false;
notifyListeners();
await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.toString());
}
},
(apiResponse) {
isExplanationsLoading = false;
if (apiResponse.messageStatus == 1 && apiResponse.data != null) {
explanationsResponse = apiResponse.data;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse.data!);
}
} else {
notifyListeners();
if (onError != null) {
onError(apiResponse.errorMessage ?? 'Failed to fetch explanations');
}
}
},
);
}
@override
void dispose() {
_tooltipTimer?.cancel();

File diff suppressed because it is too large Load Diff

@ -382,6 +382,12 @@ abstract class LocaleKeys {
static const clickHere = 'clickHere';
static const iAgreeToTheTermsAndConditions = 'iAgreeToTheTermsAndConditions';
static const iAgreeToTheTermsAndConditionsSubtitle = 'iAgreeToTheTermsAndConditionsSubtitle';
static const iAgreeAndAcceptTermsAndConditions = 'iAgreeAndAcceptTermsAndConditions';
static const termsAndConditions = 'termsAndConditions';
static const iAgreeHealthInfoCanBeUsed = 'iAgreeHealthInfoCanBeUsed';
static const privacyPolicyText = 'privacyPolicyText';
static const outPatient = 'outPatient';
static const thisInvoiceIsNotEligibleForRefund = 'thisInvoiceIsNotEligibleForRefund';
static const save = 'save';
static const userAgreement = 'userAgreement';
static const updateSuccessfully = 'updateSuccessfully';
@ -1371,7 +1377,6 @@ abstract class LocaleKeys {
static const medicalFiles = 'medicalFiles';
static const addANewFamilyMember = 'addANewFamilyMember';
static const viewInvoiceDetails = 'viewInvoiceDetails';
static const outPatient = 'outPatient';
static const invoiceDetails = 'invoiceDetails';
static const sendingEmailPleaseWait = 'sendingEmailPleaseWait';
static const emailSentSuccessfullyMessage = 'emailSentSuccessfullyMessage';
@ -1437,6 +1442,9 @@ abstract class LocaleKeys {
static const symptomsSelector = 'symptomsSelector';
static const possibleSymptomsRelatedTo = 'possibleSymptomsRelatedTo';
static const suggestionAIListDescription = 'suggestionAIListDescription';
static const whyAmIBeingAskedThis = 'whyAmIBeingAskedThis';
static const weAreAskingThisQuestionToBetterUnderstand = 'weAreAskingThisQuestionToBetterUnderstand';
static const analyzingYourSymptoms = 'analyzingYourSymptoms';
static const analyzing = 'analyzing';
static const seekMedicalAdvice = 'seekMedicalAdvice';
static const monitorDescription = 'monitorDescription';
@ -1454,6 +1462,30 @@ abstract class LocaleKeys {
static const areYouSureYouWantToExitProgress = 'areYouSureYouWantToExitProgress';
static const noQuestionAvailable = 'noQuestionAvailable';
static const possibleSymptom = 'possibleSymptom';
static const triageLevelEmergencyAmbulance = 'triageLevelEmergencyAmbulance';
static const triageLevelEmergency = 'triageLevelEmergency';
static const triageLevelConsultation24 = 'triageLevelConsultation24';
static const triageLevelConsultation = 'triageLevelConsultation';
static const triageLevelSelfCare = 'triageLevelSelfCare';
static const triageLevelEmergencyAmbulanceDesc = 'triageLevelEmergencyAmbulanceDesc';
static const triageLevelEmergencyDesc = 'triageLevelEmergencyDesc';
static const triageLevelConsultation24Desc = 'triageLevelConsultation24Desc';
static const triageLevelConsultationDesc = 'triageLevelConsultationDesc';
static const triageLevelSelfCareDesc = 'triageLevelSelfCareDesc';
static const rootCauseEmergencyEvidencePresent = 'rootCauseEmergencyEvidencePresent';
static const rootCauseSeriousEvidencePresent = 'rootCauseSeriousEvidencePresent';
static const rootCauseEmergencyConditionLikely = 'rootCauseEmergencyConditionLikely';
static const rootCauseEmergencyConditionPossible = 'rootCauseEmergencyConditionPossible';
static const rootCauseConsultationConditionLikely = 'rootCauseConsultationConditionLikely';
static const rootCauseSelfCareSufficient = 'rootCauseSelfCareSufficient';
static const rootCauseDiagnosisUnknown = 'rootCauseDiagnosisUnknown';
static const teleconsultationAvailable = 'teleconsultationAvailable';
static const seriousSymptoms = 'seriousSymptoms';
static const howTo = 'howTo';
static const explainWhy = 'explainWhy';
static const whyThisCondition = 'whyThisCondition';
static const supportingEvidence = 'supportingEvidence';
static const explanationDescription = 'explanationDescription';
static const symptomsCheckerFindingScore = 'symptomsCheckerFindingScore';
static const notSet = 'notSet';
static const years = 'years';
@ -1893,6 +1925,5 @@ abstract class LocaleKeys {
static const amountOnly = 'amountOnly';
static const invoiceHistory = 'invoiceHistory';
static const refund = 'refund';
static const thisInvoiceIsNotEligibleForRefund = 'thisInvoiceIsNotEligibleForRefund';
}

@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/gestures.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';
@ -95,7 +96,13 @@ class _RegisterNew extends State<RegisterNew> {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.showLottie(context: context, assetPath: 'assets/animations/lottie/register.json', width: 200.w, height: 200.h, fit: BoxFit.cover, repeat: true),
Utils.showLottie(
context: context,
assetPath: 'assets/animations/lottie/register.json',
width: 200.w,
height: 200.h,
fit: BoxFit.cover,
repeat: true),
SizedBox(height: 16.h),
LocaleKeys.prepareToElevate.tr(context: context).toText32(isBold: true),
SizedBox(height: 24.h),
@ -134,8 +141,10 @@ class _RegisterNew extends State<RegisterNew> {
padding: EdgeInsets.symmetric(vertical: 8.h),
leadingIcon: AppAssets.student_card,
fontFamily: "Poppins",
hasError: false, // Don't show individual field border
errorMessage: authVm.nationalIdError, // Show error message if exists
hasError: false,
// Don't show individual field border
errorMessage: authVm.nationalIdError,
// Show error message if exists
onChange: (value) {
// Clear error when user starts typing
authVm.clearNationalIdError();
@ -162,8 +171,9 @@ class _RegisterNew extends State<RegisterNew> {
authVm.clearDobError();
},
fontFamily: "Poppins",
hasError: false, // Don't show individual field border
errorMessage: authVm.dobError, // Show error message if exists
hasError: false,
// Don't show individual field border
errorMessage: authVm.dobError, // Show error message if exists
).withVerticalPadding(8),
],
),
@ -202,7 +212,7 @@ class _RegisterNew extends State<RegisterNew> {
onTap: () {
// Navigate to terms and conditions page
Utils.openWebView(
url: 'https://hmg.com/en/Pages/Terms.aspx',
url: AppUrls.hmgTermsUrlEn,
);
},
child: Text(

@ -1,5 +1,6 @@
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,18 +16,13 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/select_blood_group_widget.dart';
import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/select_city_widget.dart';
import 'package:hmg_patient_app_new/presentation/blood_donation/widgets/select_gender_widget.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:lottie/lottie.dart';
import 'package:provider/provider.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart';
class BloodDonationPage extends StatefulWidget {
BloodDonationPage({super.key});
@ -60,7 +56,8 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
onPressed: () {
if (bloodDonationVM.isUserAuthanticated()) {
bloodDonationVM.fetchHospitalsList().then((value) {
showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.selectHospital.tr(context: context), isDismissible: false, child: Consumer<BloodDonationViewModel>(builder: (_, data, __) {
showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.selectHospital.tr(context: context), isDismissible: false,
child: Consumer<BloodDonationViewModel>(builder: (_, data, __) {
return HospitalBottomSheetBodySelection(
isHideTitle: true,
onUserHospitalSelection: (BdGetProjectsHaveBdClinic userChoice) {
@ -77,7 +74,8 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Lottie.asset(AppAnimations.errorAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill),
Lottie.asset(AppAnimations.errorAnimation,
repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill),
SizedBox(height: 8.h),
(LocaleKeys.loginToUseService.tr()).toText16(color: AppColors.blackColor),
SizedBox(height: 16.h),
@ -131,7 +129,8 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
padding: EdgeInsets.all(24.w),
child: SingleChildScrollView(
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: false),
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: false),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
@ -150,7 +149,8 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
LocaleKeys.city.tr().toText16(color: AppColors.textColor, isBold: true),
(appState.isArabic()
? (bloodDonationVM.selectedCity?.descriptionN ?? LocaleKeys.selectBloodDonationCity.tr())
: bloodDonationVM.selectedCity?.description ?? LocaleKeys.selectBloodDonationCity.tr(context: context))
: bloodDonationVM.selectedCity?.description ??
LocaleKeys.selectBloodDonationCity.tr(context: context))
.toText14(color: AppColors.greyTextColor, isBold: true),
],
),
@ -160,7 +160,10 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
],
).onPress(() async {
showCommonBottomSheetWithoutHeight(context,
title: LocaleKeys.selectCity.tr(context: context), isDismissible: true, child: SelectCityWidget(bloodDonationViewModel: bloodDonationVM), callBackFunc: () {});
title: LocaleKeys.selectCity.tr(context: context),
isDismissible: true,
child: SelectCityWidget(bloodDonationViewModel: bloodDonationVM),
callBackFunc: () {});
}),
SizedBox(height: 16.h),
Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h),
@ -254,17 +257,17 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
SizedBox(width: 12.w),
Expanded(
child: LocaleKeys.aboutBloodDonation.tr(context: context).toText16(
color: AppColors.primaryRedColor,
isBold: true,
),
color: AppColors.primaryRedColor,
isBold: true,
),
),
],
),
SizedBox(height: 12.h),
LocaleKeys.bloodDonationDescription.tr(context: context).toText14(
color: AppColors.greyTextColor,
isBold: true,
),
color: AppColors.greyTextColor,
isBold: true,
),
SizedBox(height: 12.h),
Row(
children: [
@ -279,9 +282,9 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
SizedBox(width: 8.w),
Expanded(
child: LocaleKeys.bloodDonationBenefit1.tr(context: context).toText13(
color: AppColors.textColor,
isBold: true,
),
color: AppColors.textColor,
isBold: true,
),
),
],
),
@ -299,9 +302,9 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
SizedBox(width: 8.w),
Expanded(
child: LocaleKeys.bloodDonationBenefit2.tr(context: context).toText13(
color: AppColors.textColor,
isBold: true,
),
color: AppColors.textColor,
isBold: true,
),
),
],
),
@ -319,9 +322,9 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
SizedBox(width: 8.w),
Expanded(
child: LocaleKeys.bloodDonationBenefit3.tr(context: context).toText13(
color: AppColors.textColor,
isBold: true,
),
color: AppColors.textColor,
isBold: true,
),
),
],
),
@ -376,10 +379,10 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
// Navigator.of(context).pushNamed('/terms');
appState.isArabic()
? Utils.openWebView(
url: 'https://hmg.com/ar/Pages/Terms.aspx',
url: AppUrls.hmgTermsUrlAr,
)
: Utils.openWebView(
url: 'https://hmg.com/en/Pages/Terms.aspx',
url: AppUrls.hmgTermsUrlEn,
);
},
child: Text(
@ -419,7 +422,8 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Lottie.asset(AppAnimations.errorAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill),
Lottie.asset(AppAnimations.errorAnimation,
repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill),
SizedBox(height: 8.h),
(LocaleKeys.loginToUseService.tr()).toText16(color: AppColors.blackColor),
SizedBox(height: 16.h),

@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
@ -820,7 +821,7 @@ class _ServicesPageState extends State<ServicesPage> {
),
).onPress(() {
Utils.openWebView(
url: 'https://hmg.com/en/Pages/Terms.aspx',
url: AppUrls.hmgTermsUrlEn,
);
}),
),
@ -845,7 +846,7 @@ class _ServicesPageState extends State<ServicesPage> {
),
).onPress(() {
Utils.openWebView(
url: 'https://hmg.com/en/Pages/Privacy.aspx',
url: AppUrls.hmgPrivacyUrlEn,
);
}),
)

@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/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/cache_consts.dart';
@ -82,7 +83,8 @@ class MonthlyReport extends StatelessWidget {
LocaleKeys.email.tr(context: context).toText12(color: AppColors.greyTextColor, isBold: true),
SizedBox(
width: MediaQuery.of(context).size.width * 0.7,
child: "${appState.getAuthenticatedUser()!.emailAddress}".toText16(color: AppColors.textColor, isBold: true, maxlines: 2),
child: "${appState.getAuthenticatedUser()!.emailAddress}"
.toText16(color: AppColors.textColor, isBold: true, maxlines: 2),
),
],
),
@ -98,7 +100,9 @@ class MonthlyReport extends StatelessWidget {
Utils.buildSvgWithAssets(icon: AppAssets.prescription_remarks_icon, width: 18.w, height: 18.h),
SizedBox(width: 9.h),
Expanded(
child: LocaleKeys.monthlyHealthSummaryReportDisclaimer.tr(context: context).toText10(isBold: true, color: AppColors.greyTextColorLight),
child: LocaleKeys.monthlyHealthSummaryReportDisclaimer
.tr(context: context)
.toText10(isBold: true, color: AppColors.greyTextColorLight),
),
],
),
@ -135,9 +139,11 @@ class MonthlyReport extends StatelessWidget {
),
SizedBox(width: 10.w),
"I agree to the ".toText14(isBold: true, letterSpacing: -1.0),
"terms and conditions".toText14(isBold: true, letterSpacing: -1.0, color: AppColors.primaryRedColor, isUnderLine: true).onPress(() {
"terms and conditions"
.toText14(isBold: true, letterSpacing: -1.0, color: AppColors.primaryRedColor, isUnderLine: true)
.onPress(() {
Utils.openWebView(
url: 'https://hmg.com/en/Pages/Terms.aspx',
url: AppUrls.hmgTermsUrlEn,
);
})
],

@ -24,9 +24,9 @@ import 'package:provider/provider.dart';
class InvoiceListCard extends StatelessWidget {
final GetInvoicesListResponseModel getInvoicesListResponseModel;
Function? onTap;
final Function? onTap;
InvoiceListCard({super.key, required this.getInvoicesListResponseModel, required this.onTap});
const InvoiceListCard({super.key, required this.getInvoicesListResponseModel, required this.onTap});
void _handleRefundInvoiceTap(BuildContext context) {
final invoiceNo = getInvoicesListResponseModel.invoiceNo;
@ -70,7 +70,6 @@ class InvoiceListCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
AppState appState = getIt<AppState>();
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true),
child: Padding(
@ -91,7 +90,7 @@ class InvoiceListCard extends StatelessWidget {
textColor: AppColors.textColor,
),
AppCustomChipWidget(
labelText: appState.isArabic() ? "العيادات الخارجيه" : "Out Patient",
labelText: LocaleKeys.outPatient.tr(context: context),
backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.1),
textColor: AppColors.warningColorYellow,
),

@ -4,6 +4,7 @@ import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_swiper_view/flutter_swiper_view.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
@ -347,13 +348,13 @@ class ProfileSettingsState extends State<ProfileSettings> {
1.divider,
actionItem(AppAssets.privacy_terms, LocaleKeys.privacyPolicy.tr(context: context), () {
Utils.openWebView(
url: 'https://hmg.com/en/Pages/Privacy.aspx',
url: AppUrls.hmgPrivacyUrlEn,
);
}, isExternalLink: true),
1.divider,
actionItem(AppAssets.privacy_terms, LocaleKeys.termsConditoins.tr(context: context), () {
Utils.openWebView(
url: 'https://hmg.com/en/Pages/Terms.aspx',
url: AppUrls.hmgTermsUrlEn,
);
}, isExternalLink: true),
1.divider,

@ -23,6 +23,7 @@ import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_b
import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart';
import 'package:hmg_patient_app_new/presentation/emergency_services/nearest_er_page.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/condition_card.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/triage_level_card.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@ -49,11 +50,46 @@ class _PossibleConditionsPageState extends State<PossibleConditionsPage> {
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
// Clear selections once user reaches results page
symptomsCheckerViewModel.clearSelectionsKeepResults();
symptomsCheckerViewModel = context.read<SymptomsCheckerViewModel>();
// Call GetTriage API to fetch triage level
_fetchTriageLevel();
// NOTE: Do NOT clear selections here - we need the evidence for getExplanations API
// The evidence (symptoms, risk factors, suggestions) is needed when user taps "Explain, Why?" on condition cards
// symptomsCheckerViewModel.clearSelectionsForPossibleConditions();
});
}
void _fetchTriageLevel() {
// Build evidence from all selections
List<Map<String, dynamic>> evidence = [];
// Get all evidence with proper sources
final allEvidence = symptomsCheckerViewModel.getAllEvidenceWithSources();
// Convert to simple format (id + choice_id)
for (var item in allEvidence) {
evidence.add({
"id": item["id"] ?? "",
"choice_id": "present",
});
}
// Call Triage API
symptomsCheckerViewModel.fetchTriageLevel(
evidence: evidence,
onSuccess: (response) {
// Triage level data is now stored in ViewModel
// UI will update automatically via Consumer
},
onError: (error) {
// Silently fail - show conditions without triage card
debugPrint("Error fetching triage level: $error");
},
);
}
Widget _buildLoadingShimmer() {
return ListView.separated(
shrinkWrap: true,
@ -98,6 +134,12 @@ class _PossibleConditionsPageState extends State<PossibleConditionsPage> {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Triage Level Card at the top (if available)
if (symptomsCheckerViewModel.triageLevelResponse?.dataDetails != null)
TriageLevelCard(
triageLevelData: symptomsCheckerViewModel.triageLevelResponse!.dataDetails!,
),
SizedBox(height: 16.h),
ListView.separated(
padding: EdgeInsets.zero,
@ -128,8 +170,6 @@ class _PossibleConditionsPageState extends State<PossibleConditionsPage> {
return;
}
log("isBookingFromSymptomsChecker: ${symptomsCheckerViewModel.isBookingFromSymptomsChecker}");
// For non-emergency cases, continue with normal booking flow
LoaderBottomSheet.showLoader();
symptomsCheckerViewModel.getClinicConditionsFromCategory(
@ -164,7 +204,11 @@ class _PossibleConditionsPageState extends State<PossibleConditionsPage> {
loadingText: LocaleKeys.areYouSureYouWantToExitProgress.tr(context: context),
isShowActionButtons: true,
onCancelTap: () => Navigator.pop(context),
onConfirmTap: () => onConfirm(),
onConfirmTap: () {
// Clear all evidence before exiting
symptomsCheckerViewModel.clearSelectionsForPossibleConditions();
onConfirm();
},
),
callBackFunc: () {},
isFullScreen: false,
@ -301,7 +345,6 @@ class _PossibleConditionsPageState extends State<PossibleConditionsPage> {
initiateBookAppointmentFlow(BuildContext context) {
// Set flag to indicate booking is from symptoms checker
symptomsCheckerViewModel.setBookingFromSymptomsChecker(true);
// Check if clinicDetailsList is not empty before accessing
if (symptomsCheckerViewModel.clinicDetailsList.isEmpty) {
// Show error if no clinic details available

@ -122,6 +122,7 @@ class _SuggestionsScreenState extends State<SuggestionsScreen> {
Expanded(
child: LocaleKeys.suggestionAIListDescription.tr(context: context).toText12(
color: AppColors.greyInfoTextColor,
fontWeight: FontWeight.w600
),
)
],

@ -18,6 +18,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:lottie/lottie.dart';
import 'package:provider/provider.dart';
@ -158,7 +159,13 @@ class _TriagePageState extends State<TriagePage> {
}
void _onPreviousPressed() {
context.pop();
// Try to go back to previous question
bool wentBack = viewModel.goBackToPreviousTriageQuestion();
if (!wentBack) {
// If already at first question, go back to suggestions screen
context.pop();
}
}
void _onNextPressed() {
@ -225,15 +232,15 @@ class _TriagePageState extends State<TriagePage> {
}
}
// Get all evidence: initial symptoms + risk factors + suggestions + triage evidence
List<String> initialEvidenceIds = viewModel.getAllEvidenceIds();
// Get all evidence: initial symptoms + risk factors + suggestions (with sources) + triage evidence
List<Map<String, dynamic>> initialEvidenceList = viewModel.getAllEvidenceWithSources();
List<Map<String, String>> triageEvidence = viewModel.getTriageEvidence();
// Call API with updated evidence
viewModel.getDiagnosisForTriage(
age: viewModel.selectedAge!,
gender: viewModel.selectedGender!.toLowerCase() == "male" ? 1 : 2,
evidenceIds: initialEvidenceIds,
initialEvidenceList: initialEvidenceList,
triageEvidence: triageEvidence,
language: viewModel.appState.isArabic() ? 'ar' : 'en',
onSuccess: (response) {
@ -245,6 +252,159 @@ class _TriagePageState extends State<TriagePage> {
);
}
void _showRationaleBottomSheet(BuildContext context) {
// Get all current evidence to send to the API
List<Map<String, dynamic>> evidence = [];
// Add all initial evidence (symptoms + risk factors + suggestions) with sources
final initialEvidence = viewModel.getAllEvidenceWithSources();
evidence.addAll(initialEvidence);
// Add triage evidence answered so far
final triageEvidence = viewModel.getTriageEvidence();
for (var triageItem in triageEvidence) {
evidence.add({
"id": triageItem["id"] ?? "",
"choice_id": triageItem["choice_id"] ?? "present",
"source": "initial",
});
}
// Show loading state
LoaderBottomSheet.showLoader();
// Call Rationale API
viewModel.fetchRationale(
evidence: evidence,
onSuccess: (response) {
LoaderBottomSheet.hideLoader();
// Get observation params (symptoms being analyzed)
final observationParams = response.dataDetails?.observationParams ?? [];
// Build symptom names string
String symptomNames = '';
if (observationParams.isNotEmpty) {
final names = observationParams.map((param) => param.getDisplayName()).where((name) => name.isNotEmpty).toList();
final isArabic = _appState.isArabic();
final andSeparator = isArabic ? ' و ' : ' and ';
if (names.length == 1) {
symptomNames = names.first;
} else if (names.length == 2) {
symptomNames = names.join(andSeparator);
} else if (names.length > 2) {
final lastItem = names.last;
final allButLast = names.sublist(0, names.length - 1);
symptomNames = '${allButLast.join(', ')}$andSeparator$lastItem';
}
}
if (symptomNames.isEmpty) {
symptomNames = LocaleKeys.analyzingYourSymptoms.tr(context: context);
}
// Show bottom sheet with rationale
_displayRationaleBottomSheet(context, symptomNames);
},
onError: (error) {
LoaderBottomSheet.hideLoader();
dialogService.showErrorBottomSheet(message: error);
},
);
}
void _displayRationaleBottomSheet(BuildContext context, String symptomNames) {
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.whyAmIBeingAskedThis.tr(context: context),
context,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.weAreAskingThisQuestionToBetterUnderstand.tr(context: context, namedArgs: {'symptoms': symptomNames}).toText14(
color: AppColors.textColor,
),
SizedBox(height: 24.h),
CustomButton(
text: LocaleKeys.ok.tr(context: context),
onPressed: () => Navigator.pop(context),
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
),
],
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
void _showInstructionsBottomSheet(BuildContext context, TriageQuestion question) {
final instructions = question.instruction ?? [];
final hasExplication = question.explication != null && question.explication!.isNotEmpty;
final hasInstructions = instructions.isNotEmpty;
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.howTo.tr(context: context),
context,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Explication (if available)
if (hasExplication) ...[
(question.explication!).toText14(
color: AppColors.textColor,
),
if (hasInstructions) SizedBox(height: 16.h),
],
// Instructions list with bullets (if available)
if (hasInstructions) ...[
...instructions.asMap().entries.map((entry) {
final instruction = entry.value;
return Padding(
padding: EdgeInsets.only(bottom: 12.h),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.only(top: 6.h, right: 12.w, left: 12.w),
width: 6.w,
height: 6.h,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColors.primaryRedColor,
),
),
Expanded(
child: instruction.toText14(
color: AppColors.textColor,
),
),
],
),
);
}).toList(),
],
SizedBox(height: 24.h),
CustomButton(
text: LocaleKeys.ok.tr(context: context),
onPressed: () => Navigator.pop(context),
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
),
],
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
@ -401,8 +561,27 @@ class _TriagePageState extends State<TriagePage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Main question text
(question.text ?? "").toText16(isBold: true, color: AppColors.textColor),
// Main question text with optional help icon
Row(
children: [
Expanded(
child: (question.text ?? "").toText16(isBold: true, color: AppColors.textColor),
),
// Show help icon if instructions are available
if (question.hasInstructions) ...[
SizedBox(width: 8.w),
GestureDetector(
onTap: () => _showInstructionsBottomSheet(context, question),
child: Utils.buildSvgWithAssets(
icon: AppAssets.alertSquare,
height: 24.h,
width: 24.h,
iconColor: AppColors.textColor,
),
),
],
],
),
SizedBox(height: 24.h),
// Type 1: Show items as checkboxes only (no choices displayed)
@ -474,6 +653,23 @@ class _TriagePageState extends State<TriagePage> {
);
}),
],
SizedBox(height: 12.h),
GestureDetector(
onTap: () => _showRationaleBottomSheet(context),
child: Row(
children: [
Expanded(
child: LocaleKeys.whyAmIBeingAskedThis.tr(context: context).toText12(
decorationColor: AppColors.primaryRedColor,
isUnderLine: true,
color: AppColors.primaryRedColor,
fontWeight: FontWeight.w600,
),
)
],
),
),
],
),
),
@ -537,7 +733,7 @@ class _TriagePageState extends State<TriagePage> {
children: [
RichText(
text: TextSpan(
text: "${LocaleKeys.possibleSymptom.tr(context: context)} ",
text: "${LocaleKeys.possibleConditions.tr(context: context)} ",
style: TextStyle(
color: AppColors.greyTextColor, fontWeight: FontWeight.w600, fontSize: 14.f, fontFamily: isArabic ? 'CairoArabic' : 'Poppins'),
children: [
@ -587,8 +783,8 @@ class _TriagePageState extends State<TriagePage> {
Expanded(
child: CustomButton(
text: LocaleKeys.previous.tr(context: context),
onPressed: isFirstQuestion ? () {} : _onPreviousPressed,
isDisabled: isFirstQuestion || viewModel.isTriageDiagnosisLoading,
onPressed: _onPreviousPressed,
isDisabled: viewModel.isTriageDiagnosisLoading,
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11),
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,

@ -0,0 +1,231 @@
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_export.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
// Before using the checkup, please read the Terms of Service and remember:
//
// Checkup isn't a diagnosis. It's only for your information and not a qualified medical opinion.
// Checkup isn't for emergencies. Call your local emergency number right away when there's a health emergency.
// Your data is safe. Privacy measures ensure the information you share is protected.
class UserConsentScreen extends StatefulWidget {
const UserConsentScreen({super.key});
@override
State<UserConsentScreen> createState() => _UserConsentScreenState();
}
class _UserConsentScreenState extends State<UserConsentScreen> {
bool _isTermsAccepted = false;
bool _isPrivacyAccepted = false;
void _onTermsAndConditionsAgreed() {
setState(() {
_isTermsAccepted = !_isTermsAccepted;
});
}
void _onPrivacyPolicyAgreed() {
setState(() {
_isPrivacyAccepted = !_isPrivacyAccepted;
});
}
bool get _canProceed => _isTermsAccepted && _isPrivacyAccepted;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Column(
children: [
Expanded(
child: CollapsingListView(
title: LocaleKeys.termsService.tr(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Before using the checkup, please read the Terms of Service and remember:".toText14(),
SizedBox(height: 44.h),
"• Checkup isn't a diagnosis. It's only for your information and not a qualified medical opinion.".toText14(),
SizedBox(height: 12.h),
"• Checkup isn't for emergencies. Call your local emergency number right away when there's a health emergency.".toText14(),
SizedBox(height: 12.h),
"• Your data is safe. Privacy measures ensure the information you share is protected.".toText14(),
SizedBox(height: 24.h),
GestureDetector(
onTap: _onTermsAndConditionsAgreed,
child: Container(
margin: EdgeInsets.only(bottom: 12.h),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: 24.w,
height: 24.w,
decoration: BoxDecoration(
color: _isTermsAccepted ? AppColors.primaryRedColor : Colors.transparent,
borderRadius: BorderRadius.circular(5.r),
border: Border.all(
color: _isTermsAccepted ? AppColors.primaryRedColor : AppColors.checkBoxBorderColor,
width: 1.w,
),
),
child: _isTermsAccepted ? Icon(Icons.check, size: 16.f, color: AppColors.whiteColor) : null,
),
SizedBox(width: 12.w),
Expanded(
child: RichText(
text: TextSpan(
text: LocaleKeys.iAgreeAndAcceptTermsAndConditions.tr(context: context),
style: TextStyle(
fontSize: 16.f,
fontFamily: getIt.get<AppState>().isArabic() ? 'CairoArabic' : 'Poppins',
fontWeight: FontWeight.w500,
color: AppColors.textColor,
),
children: [
WidgetSpan(
child: GestureDetector(
onTap: () {
AppState appState = getIt.get<AppState>();
Utils.openWebView(
url: appState.isArabic() ? AppUrls.hmgTermsUrlAr : AppUrls.hmgTermsUrlEn,
);
},
child: Text(
LocaleKeys.termsAndConditions.tr(context: context),
style: TextStyle(
fontSize: 16.f,
fontFamily: getIt.get<AppState>().isArabic() ? 'CairoArabic' : 'Poppins',
fontWeight: FontWeight.w500,
color: AppColors.primaryRedColor,
decoration: TextDecoration.underline,
decorationColor: AppColors.primaryRedColor,
),
),
),
),
],
),
),
),
],
),
),
),
SizedBox(
height: 16.h,
),
GestureDetector(
onTap: _onPrivacyPolicyAgreed,
child: Container(
margin: EdgeInsets.only(bottom: 12.h),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: 24.w,
height: 24.w,
decoration: BoxDecoration(
color: _isPrivacyAccepted ? AppColors.primaryRedColor : Colors.transparent,
borderRadius: BorderRadius.circular(5.r),
border: Border.all(
color: _isPrivacyAccepted ? AppColors.primaryRedColor : AppColors.checkBoxBorderColor,
width: 1.w,
),
),
child: _isPrivacyAccepted ? Icon(Icons.check, size: 16.f, color: AppColors.whiteColor) : null,
),
SizedBox(width: 12.w),
Expanded(
child: RichText(
text: TextSpan(
text: LocaleKeys.iAgreeHealthInfoCanBeUsed.tr(context: context),
style: TextStyle(
fontSize: 16.f,
fontFamily: getIt.get<AppState>().isArabic() ? 'CairoArabic' : 'Poppins',
fontWeight: FontWeight.w500,
color: AppColors.textColor,
),
children: [
WidgetSpan(
child: GestureDetector(
onTap: () {
AppState appState = getIt.get<AppState>();
Utils.openWebView(
url: appState.isArabic() ? AppUrls.hmgPrivacyUrlAr : AppUrls.hmgPrivacyUrlEn,
);
},
child: Text(
LocaleKeys.privacyPolicyText.tr(context: context),
style: TextStyle(
fontSize: 16.f,
fontFamily: getIt.get<AppState>().isArabic() ? 'CairoArabic' : 'Poppins',
fontWeight: FontWeight.w500,
color: AppColors.primaryRedColor,
decorationColor: AppColors.primaryRedColor,
),
),
),
),
],
),
),
),
],
),
),
),
],
).paddingAll(24.w),
),
),
_buildStickyBottomCard(context),
],
),
);
}
Widget _buildStickyBottomCard(BuildContext context) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 16.h),
Row(
children: [
Expanded(
child: CustomButton(
text: LocaleKeys.next.tr(context: context),
onPressed: _canProceed ? () => context.navigateWithName(AppRoutes.userInfoSelection) : null,
isDisabled: !_canProceed,
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 16.f,
),
),
],
),
SizedBox(height: 24.h),
],
).paddingSymmetrical(24.w, 0),
);
}
}

@ -17,14 +17,14 @@ import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:provider/provider.dart';
class UserInfoSelectionPage extends StatefulWidget {
const UserInfoSelectionPage({super.key});
class UserInfoSelectionScreen extends StatefulWidget {
const UserInfoSelectionScreen({super.key});
@override
State<UserInfoSelectionPage> createState() => _UserInfoSelectionPageState();
State<UserInfoSelectionScreen> createState() => _UserInfoSelectionScreenState();
}
class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
@override
void initState() {
super.initState();
@ -202,10 +202,15 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 16.w),
child: Column(
children: [
LocaleKeys.helloIsYourInformationUpToDate.tr(namedArgs: {'name': name}).toText16(
weight: FontWeight.w600,
color: AppColors.textColor,
),
LocaleKeys.helloIsYourInformationUpToDate
.tr(namedArgs: {'name': name})
.toText16(
weight: FontWeight.w600,
color: AppColors.textColor,
)
.onPress(() {
context.navigateWithName(AppRoutes.userConsentScreen);
}),
SizedBox(height: 32.h),
_buildEditInfoTile(
context: context,

@ -8,11 +8,16 @@ import 'package:hmg_patient_app_new/core/enums.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/symptoms_checker/models/resp_models/triage_response_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/symptoms_checker/widgets/custom_progress_bar.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:provider/provider.dart';
class ConditionCard extends StatelessWidget {
final TriageCondition condition;
@ -247,6 +252,16 @@ class ConditionCard extends StatelessWidget {
LocaleKeys.description.tr(context: context).toText14(weight: FontWeight.bold, color: AppColors.textColor),
SizedBox(height: 2.h),
description.toText12(isBold: true, color: AppColors.greyTextColor),
SizedBox(height: 8.h),
GestureDetector(
onTap: () => _showExplanationBottomSheet(context),
child: LocaleKeys.explainWhy.tr(context: context).toText12(
isBold: true,
color: AppColors.primaryRedColor,
isUnderLine: true,
decorationColor: AppColors.primaryRedColor,
),
),
if (severityEnum != PossibleConditionsSeverityEnum.monitorOnly)
CustomButton(
text: LocaleKeys.bookAppointment.tr(context: context),
@ -283,4 +298,107 @@ class ConditionCard extends StatelessWidget {
),
);
}
void _showExplanationBottomSheet(BuildContext context) {
final viewModel = context.read<SymptomsCheckerViewModel>();
final conditionId = condition.id ?? "";
if (conditionId.isEmpty) {
final dialogService = getIt<DialogService>();
dialogService.showErrorBottomSheet(
message: "Condition ID not available",
);
return;
}
// Get all evidence with proper sources
final allEvidenceWithSources = viewModel.getAllEvidenceWithSources();
// Convert to format expected by GetExplanations API (with choice_id)
final evidence = allEvidenceWithSources.map((item) {
return {
"id": item["id"] ?? "",
"choice_id": "present",
"source": item["source"] ?? "initial",
};
}).toList();
// Show loading
LoaderBottomSheet.showLoader();
// Call Explanations API
viewModel.fetchExplanations(
targetConditionId: conditionId,
evidence: evidence,
onSuccess: (response) {
LoaderBottomSheet.hideLoader();
final supportingEvidence = response.dataDetails?.supportingEvidence ?? [];
// Show bottom sheet with supporting evidence
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.whyThisCondition.tr(context: context),
context,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Human-understandable description
LocaleKeys.explanationDescription.tr(context: context).toText14(
color: AppColors.textColor,
),
SizedBox(height: 12.h),
// Supporting Evidence List
if (supportingEvidence.isEmpty)
LocaleKeys.noDataAvailable.tr(context: context).toText14(
color: AppColors.greyTextColor,
)
else
...supportingEvidence.map((evidence) {
return Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.only(top: 6.h, right: 12.w, left: 12.w),
width: 6.w,
height: 6.h,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColors.textColor,
),
),
Expanded(
child: (evidence.getDisplayName()).toText14(
color: AppColors.textColor,
),
),
],
),
);
}).toList(),
SizedBox(height: 24.h),
CustomButton(
text: LocaleKeys.ok.tr(context: context),
onPressed: () => Navigator.pop(context),
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
),
],
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onError: (error) {
LoaderBottomSheet.hideLoader();
final dialogService = getIt<DialogService>();
dialogService.showErrorBottomSheet(message: error);
},
);
}
}

@ -0,0 +1,280 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_level_response_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
class TriageLevelCard extends StatefulWidget {
final TriageLevelDataDetails triageLevelData;
const TriageLevelCard({super.key, required this.triageLevelData});
@override
State<TriageLevelCard> createState() => _TriageLevelCardState();
}
class _TriageLevelCardState extends State<TriageLevelCard> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _borderAnimation;
@override
void initState() {
super.initState();
// Create animation controller for border pulse effect
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 2000),
);
// Create animation that goes from 1.0 to 0.3 and back (for opacity)
_borderAnimation = Tween<double>(begin: 1.0, end: 0.3).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
),
);
// Start the animation and repeat
_animationController.repeat(reverse: true);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
/// Get localized title based on triage level
String getTitle(BuildContext context) {
final triageLevel = widget.triageLevelData.triageLevel ?? '';
switch (triageLevel) {
case 'emergency_ambulance':
return LocaleKeys.triageLevelEmergencyAmbulance.tr(context: context);
case 'emergency':
return LocaleKeys.triageLevelEmergency.tr(context: context);
case 'consultation_24':
return LocaleKeys.triageLevelConsultation24.tr(context: context);
case 'consultation':
return LocaleKeys.triageLevelConsultation.tr(context: context);
case 'self_care':
return LocaleKeys.triageLevelSelfCare.tr(context: context);
default:
return LocaleKeys.triageLevelConsultation.tr(context: context);
}
}
/// Get localized description based on triage level
String getDescription(BuildContext context) {
final triageLevel = widget.triageLevelData.triageLevel ?? '';
switch (triageLevel) {
case 'emergency_ambulance':
return LocaleKeys.triageLevelEmergencyAmbulanceDesc.tr(context: context);
case 'emergency':
return LocaleKeys.triageLevelEmergencyDesc.tr(context: context);
case 'consultation_24':
return LocaleKeys.triageLevelConsultation24Desc.tr(context: context);
case 'consultation':
return LocaleKeys.triageLevelConsultationDesc.tr(context: context);
case 'self_care':
return LocaleKeys.triageLevelSelfCareDesc.tr(context: context);
default:
return LocaleKeys.triageLevelConsultationDesc.tr(context: context);
}
}
/// Get color based on triage level
Color getBackgroundColor() {
final triageLevel = widget.triageLevelData.triageLevel ?? '';
switch (triageLevel) {
case 'emergency_ambulance':
case 'emergency':
return AppColors.chipColorEmergency.withValues(alpha: 0.1);
case 'consultation_24':
case 'consultation':
return AppColors.chipColorSeekMedicalAdvice.withValues(alpha: 0.1);
case 'self_care':
return AppColors.chipColorMonitor.withValues(alpha: 0.1);
default:
return AppColors.chipColorSeekMedicalAdvice.withValues(alpha: 0.1);
}
}
/// Get border color based on triage level
Color getBorderColor() {
final triageLevel = widget.triageLevelData.triageLevel ?? '';
switch (triageLevel) {
case 'emergency_ambulance':
case 'emergency':
return AppColors.chipColorEmergency;
case 'consultation_24':
case 'consultation':
return AppColors.chipColorSeekMedicalAdvice;
case 'self_care':
return AppColors.chipColorMonitor;
default:
return AppColors.chipColorSeekMedicalAdvice;
}
}
/// Get icon based on triage level
IconData getIcon() {
final triageLevel = widget.triageLevelData.triageLevel ?? '';
switch (triageLevel) {
case 'emergency_ambulance':
return Icons.local_hospital;
case 'emergency':
return Icons.emergency;
case 'consultation_24':
return Icons.schedule;
case 'consultation':
return Icons.medical_services;
case 'self_care':
return Icons.self_improvement;
default:
return Icons.medical_services;
}
}
/// Get root cause text (localized)
String? getRootCauseText(BuildContext context) {
final rootCause = widget.triageLevelData.rootCause ?? '';
switch (rootCause) {
case 'emergency_evidence_present':
return LocaleKeys.rootCauseEmergencyEvidencePresent.tr(context: context);
case 'serious_evidence_present':
return LocaleKeys.rootCauseSeriousEvidencePresent.tr(context: context);
case 'emergency_condition_likely':
return LocaleKeys.rootCauseEmergencyConditionLikely.tr(context: context);
case 'emergency_condition_possible':
return LocaleKeys.rootCauseEmergencyConditionPossible.tr(context: context);
case 'consultation_condition_likely':
return LocaleKeys.rootCauseConsultationConditionLikely.tr(context: context);
case 'self_care_sufficient':
return LocaleKeys.rootCauseSelfCareSufficient.tr(context: context);
case 'diagnosis_unknown':
return LocaleKeys.rootCauseDiagnosisUnknown.tr(context: context);
default:
return null;
}
}
@override
Widget build(BuildContext context) {
final borderColor = getBorderColor();
final title = getTitle(context);
final description = getDescription(context);
final rootCauseText = getRootCauseText(context);
final seriousSymptoms = widget.triageLevelData.serious ?? [];
return AnimatedBuilder(
animation: _borderAnimation,
builder: (context, child) {
return Container(
width: double.infinity,
margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 8.h),
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(24.r),
border: Border.all(
color: borderColor.withValues(alpha: _borderAnimation.value),
width: 2.w,
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.08),
blurRadius: 10.r,
offset: Offset(0, 2.h),
),
],
),
child: child,
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: title.toText18(isBold: true, color: AppColors.textColor),
),
],
),
SizedBox(height: 12.h),
// Description
description.toText14(color: AppColors.textColor),
// Root Cause (if available)
if (rootCauseText != null) ...[
SizedBox(height: 12.h),
Container(
padding: EdgeInsets.symmetric(horizontal: 0.w, vertical: 8.h),
decoration: BoxDecoration(
color: AppColors.whiteColor.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(8.r),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.info_outline,
size: 16.f,
color: borderColor,
),
SizedBox(width: 8.w),
Flexible(
child: rootCauseText.toText12(
color: AppColors.textColor,
isBold: true,
),
),
],
),
),
],
// Serious Symptoms (if any)
if (seriousSymptoms.isNotEmpty) ...[
SizedBox(height: 16.h),
LocaleKeys.seriousSymptoms.tr(context: context).toText14(
isBold: true,
color: borderColor,
),
SizedBox(height: 8.h),
...seriousSymptoms.map((symptom) {
return Padding(
padding: EdgeInsets.only(bottom: 4.h),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.only(top: 6.h, right: 8.w, left: 8.w),
width: 6.w,
height: 6.h,
decoration: BoxDecoration(shape: BoxShape.circle, color: borderColor),
),
Expanded(
child: (symptom.getDisplayName()).toText13(color: AppColors.textColor),
),
],
),
);
}),
],
],
),
);
}
}

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoices_list_response_model.dart';
import 'package:hmg_patient_app_new/features/refund_request/models/resp_models/refund_request_list_response_model.dart';
import 'package:hmg_patient_app_new/presentation/authentication/login.dart';
import 'package:hmg_patient_app_new/presentation/authentication/register.dart';
import 'package:hmg_patient_app_new/presentation/authentication/register_step2.dart';
@ -24,7 +25,6 @@ import 'package:hmg_patient_app_new/presentation/my_family/my_family.dart';
import 'package:hmg_patient_app_new/presentation/parking/paking_page.dart';
import 'package:hmg_patient_app_new/presentation/refund_request/refund_request_create_page.dart';
import 'package:hmg_patient_app_new/presentation/refund_request/refund_request_detail_screen.dart';
import 'package:hmg_patient_app_new/features/refund_request/models/resp_models/refund_request_list_response_model.dart';
import 'package:hmg_patient_app_new/presentation/service_price/services_price_list_page.dart';
import 'package:hmg_patient_app_new/presentation/smartwatches/smartwatch_home_page.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/organ_selector_screen.dart';
@ -33,8 +33,9 @@ import 'package:hmg_patient_app_new/presentation/symptoms_checker/risk_factors_s
import 'package:hmg_patient_app_new/presentation/symptoms_checker/suggestions_screen.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/symptoms_selector_screen.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/triage_screen.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_consent_screen.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection_screen.dart';
import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_screen.dart';
import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_page.dart';
import 'package:hmg_patient_app_new/presentation/water_monitor/water_consumption_page.dart';
@ -85,6 +86,7 @@ class AppRoutes {
static const String possibleConditionsPage = '/possibleConditionsScreen';
static const String triagePage = '/triageProgressScreen';
static const String userInfoSelection = '/userInfoSelection';
static const String userConsentScreen = '/userConsentScreen';
static const String userInfoFlowManager = '/userInfoFlowManager';
static const String monthlyReports = '/monthlyReportsPage';
static const String qrParking = '/qrParkingPage';
@ -132,7 +134,8 @@ class AppRoutes {
bloodDonationPage: (context) => BloodDonationPage(),
myChildVaccine: (context) => MyChildVaccinePage(),
bookAppointmentPage: (context) => BookAppointmentPage(),
userInfoSelection: (context) => UserInfoSelectionPage(),
userInfoSelection: (context) => UserInfoSelectionScreen(),
userConsentScreen: (context) => UserConsentScreen(),
userInfoFlowManager: (context) => UserInfoFlowManager(),
smartWatches: (context) => SmartwatchHomePage(),
// huaweiHealthExample: (context) => HuaweiHealthExample(),

@ -129,6 +129,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.13.0"
clarity_flutter:
dependency: "direct main"
description:
name: clarity_flutter
sha256: b3a114fec6e873caf03cd59a32953f8cb4b1693bd3b4f749ef3030fea027ebce
url: "https://pub.dev"
source: hosted
version: "1.9.0"
clock:
dependency: transitive
description:
@ -1041,6 +1049,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.12.0+301"
image:
dependency: transitive
description:
name: image
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
url: "https://pub.dev"
source: hosted
version: "4.8.0"
image_picker:
dependency: "direct main"
description:
@ -2150,6 +2166,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.6.1"
xxh3:
dependency: transitive
description:
name: xxh3
sha256: "399a0438f5d426785723c99da6b16e136f4953fb1e9db0bf270bd41dd4619916"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
sdks:
dart: ">=3.9.0 <4.0.0"
flutter: ">=3.35.0"

@ -178,3 +178,4 @@ flutter:
# SA8345000000815089206001
Loading…
Cancel
Save