From 14f3c6cb81fa6546e87498249e50bcfba6208bab Mon Sep 17 00:00:00 2001 From: faizatflutter Date: Wed, 21 Jan 2026 23:15:25 +0300 Subject: [PATCH] Added Arabic Support for symptoms checker --- assets/langs/ar-SA.json | 12 ++ assets/langs/en-US.json | 12 ++ convert_json.dart | 146 +++++++++--------- lib/core/api/api_client.dart | 2 +- .../data/organ_mapping_data.dart | 102 ++++++++++++ .../symptoms_checker/models/organ_model.dart | 18 +++ .../risk_and_suggestions_response_model.dart | 15 +- .../resp_models/triage_response_model.dart | 16 ++ .../symptoms_checker_view_model.dart | 45 +++++- lib/generated/locale_keys.g.dart | 11 ++ .../organ_selector_screen.dart | 77 +++++---- .../possible_conditions_screen.dart | 22 +-- .../symptoms_checker/risk_factors_screen.dart | 7 +- .../symptoms_checker/suggestions_screen.dart | 9 +- .../symptoms_selector_screen.dart | 14 +- .../symptoms_checker/triage_screen.dart | 8 +- .../symptoms_checker/user_info_selection.dart | 38 +++-- .../pages/gender_selection_page.dart | 49 +++++- .../user_info_flow_manager.dart | 29 ++-- .../widgets/condition_card.dart | 19 ++- .../widgets/interactive_body_widget.dart | 7 +- lib/widgets/buttons/custom_button.dart | 18 ++- 22 files changed, 497 insertions(+), 179 deletions(-) diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index e0b0d8a..3ceb2e9 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1377,6 +1377,18 @@ "suggestions": "الاقتراحات", "pleaseGoBackAndSelectOrgansFirst": "يرجى العودة واختيار الأعضاء أولاً", "symptomsSelector": "محدد الأعراض", + "possibleSymptomsRelatedTo": "الأعراض المحتملة المتعلقة بـ", + "suggestionAIListDescription": "هذه قائمة بالأعراض المقترحة من قبل الذكاء الاصطناعي، بناءً على المعلومات التي تم جمعها حتى الآن خلال المقابلة", + "analyzing": "جاري التحليل...", + "seekMedicalAdvice": "اطلب المشورة الطبية", + "monitorDescription": "لا حاجة لطلب المشورة الطبية. فقط حافظ على روتين صحي.", + "seekMedicalAdviceDescription": "ليست حالة طوارئ ولكن من الأفضل مراقبة الأعراض.", + "emergencyDescription": "تحتاج إلى استشارة الطبيب في أقرب وقت ممكن قبل فوات الأوان.", + "colorScience": "دلالات الألوان", + "noNeedToVisitDoctor": "لا حاجة لزي��رة الطبيب", + "commonSymptom": "الأعراض الشائعة", + "bookAppointment": "احجز موعد", + "notApplicable": "لا ينطبق", "emergencyTriage": "طوارئ", "emergencyEvidenceDetected": "تم اكتشاف دليل طوارئ. يرجى طلب العناية الطبية.", "noQuestionItemsAvailable": "لا توجد عناصر أسئلة متاحة", diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index fa59b19..50d4481 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -1367,6 +1367,18 @@ "suggestions": "Suggestions", "pleaseGoBackAndSelectOrgansFirst": "Please go back and select organs first", "symptomsSelector": "Symptoms Selector", + "possibleSymptomsRelatedTo": "Possible symptoms related to", + "suggestionAIListDescription": "This is a list of symptoms suggested by our AI, based on the information gathered so far during the interview", + "analyzing": "Analyzing...", + "seekMedicalAdvice": "Seek Medical Advice", + "monitorDescription": "No need to seek medical advice. Just keep healthy routine.", + "seekMedicalAdviceDescription": "Not emergency but better to monitor the symptoms.", + "emergencyDescription": "Need to consult doctor as soon as possible before getting too late.", + "colorScience": "Color Science", + "noNeedToVisitDoctor": "No need to visit doctor", + "commonSymptom": "Common Symptom", + "bookAppointment": "Book Appointment", + "notApplicable": "Not Applicable", "emergencyTriage": "Emergency", "emergencyEvidenceDetected": "Emergency evidence detected. Please seek medical attention.", "noQuestionItemsAvailable": "No question items available", diff --git a/convert_json.dart b/convert_json.dart index 08f344d..af5c594 100644 --- a/convert_json.dart +++ b/convert_json.dart @@ -1,73 +1,73 @@ -import 'dart:convert'; -import 'dart:io'; - -void main() async { - final file = File('assets/json/body_symptoms_data.json'); - final content = await file.readAsString(); - - print('File size: ${content.length} characters'); - - // Split into two parts - final parts = content.split('export const SymptomsData = ['); - if (parts.length != 2) { - print('ERROR: Could not split file properly'); - exit(1); - } - - print('Split into ${parts.length} parts'); - - // Process BodySymptomsData - var bodyPart = parts[0] - .replaceAll('export const BodySymptomsData = ', '') - .trim() - .replaceAll(RegExp(r';$'), ''); - - print('Parsing body symptoms...'); - Map bodySymptomsData; - try { - bodySymptomsData = json.decode(bodyPart); - print('✅ Body symptoms parsed: ${bodySymptomsData.length} body parts'); - } catch (e) { - print('❌ Error parsing body symptoms: $e'); - exit(1); - } - - // Process SymptomsData - print('Processing symptoms data...'); - var symptomsPart = parts[1] - .trim() - .replaceAll(RegExp(r'\];$'), '') - .replaceAllMapped(RegExp(r'\n(\s*)id:'), (m) => '\n${m.group(1)}"id":') - .replaceAllMapped(RegExp(r'\n(\s*)type:'), (m) => '\n${m.group(1)}"type":') - .replaceAllMapped(RegExp(r'\n(\s*)name:'), (m) => '\n${m.group(1)}"name":') - .replaceAllMapped(RegExp(r'\n(\s*)common_name:'), (m) => '\n${m.group(1)}"common_name":'); - - print('Parsing symptoms...'); - List symptomsData; - try { - symptomsData = json.decode('[$symptomsPart]'); - print('✅ Symptoms parsed: ${symptomsData.length} symptoms'); - } catch (e) { - print('❌ Error parsing symptoms: $e'); - print('First 1000 chars:'); - print('[$symptomsPart]'.substring(0, 1000)); - exit(1); - } - - // Create final JSON - print('Creating final JSON structure...'); - final jsonStructure = { - 'bodySymptoms': bodySymptomsData, - 'symptoms': symptomsData, - }; - - // Write to file - print('Writing to file...'); - final encoder = JsonEncoder.withIndent(' '); - await file.writeAsString(encoder.convert(jsonStructure)); - - print('\n✅ SUCCESS! File converted to proper JSON'); - print('✅ Body parts: ${bodySymptomsData.length}'); - print('✅ Symptoms: ${symptomsData.length}'); -} - +// import 'dart:convert'; +// import 'dart:io'; +// +// void main() async { +// final file = File('assets/json/body_symptoms_data.json'); +// final content = await file.readAsString(); +// +// print('File size: ${content.length} characters'); +// +// // Split into two parts +// final parts = content.split('export const SymptomsData = ['); +// if (parts.length != 2) { +// print('ERROR: Could not split file properly'); +// exit(1); +// } +// +// print('Split into ${parts.length} parts'); +// +// // Process BodySymptomsData +// var bodyPart = parts[0] +// .replaceAll('export const BodySymptomsData = ', '') +// .trim() +// .replaceAll(RegExp(r';$'), ''); +// +// print('Parsing body symptoms...'); +// Map bodySymptomsData; +// try { +// bodySymptomsData = json.decode(bodyPart); +// print('✅ Body symptoms parsed: ${bodySymptomsData.length} body parts'); +// } catch (e) { +// print('❌ Error parsing body symptoms: $e'); +// exit(1); +// } +// +// // Process SymptomsData +// print('Processing symptoms data...'); +// var symptomsPart = parts[1] +// .trim() +// .replaceAll(RegExp(r'\];$'), '') +// .replaceAllMapped(RegExp(r'\n(\s*)id:'), (m) => '\n${m.group(1)}"id":') +// .replaceAllMapped(RegExp(r'\n(\s*)type:'), (m) => '\n${m.group(1)}"type":') +// .replaceAllMapped(RegExp(r'\n(\s*)name:'), (m) => '\n${m.group(1)}"name":') +// .replaceAllMapped(RegExp(r'\n(\s*)common_name:'), (m) => '\n${m.group(1)}"common_name":'); +// +// print('Parsing symptoms...'); +// List symptomsData; +// try { +// symptomsData = json.decode('[$symptomsPart]'); +// print('✅ Symptoms parsed: ${symptomsData.length} symptoms'); +// } catch (e) { +// print('❌ Error parsing symptoms: $e'); +// print('First 1000 chars:'); +// print('[$symptomsPart]'.substring(0, 1000)); +// exit(1); +// } +// +// // Create final JSON +// print('Creating final JSON structure...'); +// final jsonStructure = { +// 'bodySymptoms': bodySymptomsData, +// 'symptoms': symptomsData, +// }; +// +// // Write to file +// print('Writing to file...'); +// final encoder = JsonEncoder.withIndent(' '); +// await file.writeAsString(encoder.convert(jsonStructure)); +// +// print('\n✅ SUCCESS! File converted to proper JSON'); +// print('✅ Body parts: ${bodySymptomsData.length}'); +// print('✅ Symptoms: ${symptomsData.length}'); +// } +// diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 66aa34a..39a6177 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -189,7 +189,7 @@ class ApiClientImp implements ApiClient { // body['SessionID'] = "45786230487560q"; } - body['IsNewFlutterApp'] = true; + // body['IsNewFlutterApp'] = true; body.removeWhere((key, value) => value == null); } diff --git a/lib/features/symptoms_checker/data/organ_mapping_data.dart b/lib/features/symptoms_checker/data/organ_mapping_data.dart index 6b50b25..c338a94 100644 --- a/lib/features/symptoms_checker/data/organ_mapping_data.dart +++ b/lib/features/symptoms_checker/data/organ_mapping_data.dart @@ -8,28 +8,36 @@ class OrganData { OrganModel( id: 'head', name: 'whole_head', + nameAr: 'الرأس_كامل', description: 'Head', + descriptionAr: 'الرأس', bodyView: BodyView.front, position: OrganPosition(x: 0.50, y: 0.03), ), OrganModel( id: 'left_eye', name: 'eyes', + nameAr: 'العيون', description: 'Left Eye', + descriptionAr: 'العين اليسرى', bodyView: BodyView.front, position: OrganPosition(x: 0.43, y: 0.07), ), OrganModel( id: 'right_eye', name: 'eyes', + nameAr: 'العيون', description: 'Right Eye', + descriptionAr: 'العين اليمنى', bodyView: BodyView.front, position: OrganPosition(x: 0.57, y: 0.07), ), OrganModel( id: 'nose_mouth', name: 'oral_cavity', + nameAr: 'تجويف_الفم', description: 'Nose/Mouth', + descriptionAr: 'الأنف/الفم', bodyView: BodyView.front, position: OrganPosition(x: 0.50, y: 0.09), ), @@ -38,7 +46,9 @@ class OrganData { OrganModel( id: 'throat', name: 'neck_or_throat', + nameAr: 'الرقبة_أو_الحلق', description: 'Throat', + descriptionAr: 'الحلق', bodyView: BodyView.front, position: OrganPosition(x: 0.50, y: 0.15), ), @@ -47,14 +57,18 @@ class OrganData { OrganModel( id: 'left_shoulder', name: 'upper_arm', + nameAr: 'الذراع_العلوي', description: 'Left Shoulder', + descriptionAr: 'الكتف الأيسر', bodyView: BodyView.front, position: OrganPosition(x: 0.22, y: 0.22), ), OrganModel( id: 'right_shoulder', name: 'upper_arm', + nameAr: 'الذراع_العلوي', description: 'Right Shoulder', + descriptionAr: 'الكتف الأيمن', bodyView: BodyView.front, position: OrganPosition(x: 0.8, y: 0.22), ), @@ -63,21 +77,27 @@ class OrganData { OrganModel( id: 'left_chest', name: 'chest', + nameAr: 'الصدر', description: 'Left Chest', + descriptionAr: 'الصدر الأيسر', bodyView: BodyView.front, position: OrganPosition(x: 0.38, y: 0.22), ), OrganModel( id: 'center_chest', name: 'chest', + nameAr: 'الصدر', description: 'Center Chest', + descriptionAr: 'وسط الصدر', bodyView: BodyView.front, position: OrganPosition(x: 0.52, y: 0.25), ), OrganModel( id: 'right_chest', name: 'chest', + nameAr: 'الصدر', description: 'Right Chest', + descriptionAr: 'الصدر الأيمن', bodyView: BodyView.front, position: OrganPosition(x: 0.65, y: 0.22), ), @@ -86,14 +106,18 @@ class OrganData { OrganModel( id: 'left_ribs', name: 'chest', + nameAr: 'الصدر', description: 'Left Ribs', + descriptionAr: 'الأضلاع اليسرى', bodyView: BodyView.front, position: OrganPosition(x: 0.38, y: 0.35), ), OrganModel( id: 'right_ribs', name: 'chest', + nameAr: 'الصدر', description: 'Right Ribs', + descriptionAr: 'الأضلاع اليمنى', bodyView: BodyView.front, position: OrganPosition(x: 0.64, y: 0.35), ), @@ -102,21 +126,27 @@ class OrganData { OrganModel( id: 'upper_abdomen', name: 'upper_abdomen', + nameAr: 'البطن_العلوي', description: 'Upper Abdomen', + descriptionAr: 'أعلى البطن', bodyView: BodyView.front, position: OrganPosition(x: 0.51, y: 0.31), ), OrganModel( id: 'navel', name: 'mid_abdomen', + nameAr: 'البطن_الأوسط', description: 'Navel', + descriptionAr: 'السرة', bodyView: BodyView.front, position: OrganPosition(x: 0.51, y: 0.38), ), OrganModel( id: 'lower_abdomen', name: 'lower_abdomen', + nameAr: 'البطن_السفلي', description: 'Lower Abdomen', + descriptionAr: 'أسفل البطن', bodyView: BodyView.front, position: OrganPosition(x: 0.51, y: 0.44), ), @@ -125,14 +155,18 @@ class OrganData { OrganModel( id: 'left_groin', name: 'sexual_organs', + nameAr: 'الأعضاء_التناسلية', description: 'Left Groin', + descriptionAr: 'الفخذ الأيسر', bodyView: BodyView.front, position: OrganPosition(x: 0.42, y: 0.49), ), OrganModel( id: 'right_groin', name: 'sexual_organs', + nameAr: 'الأعضاء_التناسلية', description: 'Right Groin', + descriptionAr: 'الفخذ الأيمن', bodyView: BodyView.front, position: OrganPosition(x: 0.59, y: 0.49), ), @@ -142,21 +176,27 @@ class OrganData { OrganModel( id: 'left_elbow', name: 'elbow', + nameAr: 'الكوع', description: 'Left Elbow', + descriptionAr: 'الكوع الأيسر', bodyView: BodyView.front, position: OrganPosition(x: 0.21, y: 0.35), ), OrganModel( id: 'left_forearm', name: 'forearm', + nameAr: 'الساعد', description: 'Left Forearm', + descriptionAr: 'الساعد الأيسر', bodyView: BodyView.front, position: OrganPosition(x: 0.19, y: 0.43), ), OrganModel( id: 'left_wrist', name: 'hand', + nameAr: 'اليد', description: 'Left Wrist', + descriptionAr: 'المعصم الأيسر', bodyView: BodyView.front, position: OrganPosition(x: 0.14, y: 0.5), ), @@ -166,21 +206,27 @@ class OrganData { OrganModel( id: 'right_elbow', name: 'elbow', + nameAr: 'الكوع', description: 'Right Elbow', + descriptionAr: 'الكوع الأيمن', bodyView: BodyView.front, position: OrganPosition(x: 0.79, y: 0.35), ), OrganModel( id: 'right_forearm', name: 'forearm', + nameAr: 'الساعد', description: 'Right Forearm', + descriptionAr: 'الساعد الأيمن', bodyView: BodyView.front, position: OrganPosition(x: 0.81, y: 0.43), ), OrganModel( id: 'right_wrist', name: 'hand', + nameAr: 'اليد', description: 'Right Wrist', + descriptionAr: 'المعصم الأيمن', bodyView: BodyView.front, position: OrganPosition(x: 0.85, y: 0.5), ), @@ -189,28 +235,36 @@ class OrganData { OrganModel( id: 'left_thigh', name: 'thigh', + nameAr: 'الفخذ', description: 'Left Thigh', + descriptionAr: 'الفخذ الأيسر', bodyView: BodyView.front, position: OrganPosition(x: 0.4, y: 0.60), ), OrganModel( id: 'left_knee', name: 'knee', + nameAr: 'الركبة', description: 'Left Knee', + descriptionAr: 'الركبة اليسرى', bodyView: BodyView.front, position: OrganPosition(x: 0.4, y: 0.72), ), OrganModel( id: 'left_shin', name: 'lower_leg', + nameAr: 'الساق_السفلي', description: 'Left Shin', + descriptionAr: 'الساق اليسرى', bodyView: BodyView.front, position: OrganPosition(x: 0.41, y: 0.82), ), OrganModel( id: 'left_ankle', name: 'foot', + nameAr: 'القدم', description: 'Left Ankle', + descriptionAr: 'الكاحل الأيسر', bodyView: BodyView.front, position: OrganPosition(x: 0.43, y: 0.91), ), @@ -219,28 +273,36 @@ class OrganData { OrganModel( id: 'right_thigh', name: 'thigh', + nameAr: 'الفخذ', description: 'Right Thigh', + descriptionAr: 'الفخذ الأيمن', bodyView: BodyView.front, position: OrganPosition(x: 0.65, y: 0.60), ), OrganModel( id: 'right_knee', name: 'knee', + nameAr: 'الركبة', description: 'Right Knee', + descriptionAr: 'الركبة اليمنى', bodyView: BodyView.front, position: OrganPosition(x: 0.6, y: 0.72), ), OrganModel( id: 'right_shin', name: 'lower_leg', + nameAr: 'الساق_السفلي', description: 'Right Shin', + descriptionAr: 'الساق اليمنى', bodyView: BodyView.front, position: OrganPosition(x: 0.57, y: 0.82), ), OrganModel( id: 'right_ankle', name: 'foot', + nameAr: 'القدم', description: 'Right Ankle', + descriptionAr: 'الكاحل الأيمن', bodyView: BodyView.front, position: OrganPosition(x: 0.57, y: 0.91), ), @@ -252,14 +314,18 @@ class OrganData { OrganModel( id: 'back_of_head', name: 'whole_head', + nameAr: 'الرأس_كامل', description: 'Back of Head', + descriptionAr: 'مؤخرة الرأس', bodyView: BodyView.back, position: OrganPosition(x: 0.50, y: 0.04), ), OrganModel( id: 'neck_back', name: 'nape_of_neck', + nameAr: 'مؤخرة_الرقبة', description: 'Neck', + descriptionAr: 'الرقبة', bodyView: BodyView.back, position: OrganPosition(x: 0.50, y: 0.14), ), @@ -268,14 +334,18 @@ class OrganData { OrganModel( id: 'left_shoulder_back', name: 'upper_arm', + nameAr: 'الذراع_العلوي', description: 'Left Shoulder', + descriptionAr: 'الكتف الأيسر', bodyView: BodyView.back, position: OrganPosition(x: 0.33, y: 0.19), ), OrganModel( id: 'right_shoulder_back', name: 'upper_arm', + nameAr: 'الذراع_العلوي', description: 'Right Shoulder', + descriptionAr: 'الكتف الأيمن', bodyView: BodyView.back, position: OrganPosition(x: 0.67, y: 0.19), ), @@ -284,21 +354,27 @@ class OrganData { OrganModel( id: 'upper_spine', name: 'back', + nameAr: 'الظهر', description: 'Upper Spine', + descriptionAr: 'العمود الفقري العلوي', bodyView: BodyView.back, position: OrganPosition(x: 0.50, y: 0.23), ), OrganModel( id: 'left_upper_back', name: 'back', + nameAr: 'الظهر', description: 'Left Upper Back', + descriptionAr: 'أعلى الظهر الأيسر', bodyView: BodyView.back, position: OrganPosition(x: 0.38, y: 0.28), ), OrganModel( id: 'right_upper_back', name: 'back', + nameAr: 'الظهر', description: 'Right Upper Back', + descriptionAr: 'أعلى الظهر الأيمن', bodyView: BodyView.back, position: OrganPosition(x: 0.62, y: 0.28), ), @@ -307,21 +383,27 @@ class OrganData { OrganModel( id: 'mid_spine', name: 'back', + nameAr: 'الظهر', description: 'Mid Spine', + descriptionAr: 'العمود الفقري الأوسط', bodyView: BodyView.back, position: OrganPosition(x: 0.50, y: 0.35), ), OrganModel( id: 'left_mid_back', name: 'back', + nameAr: 'الظهر', description: 'Left Mid Back', + descriptionAr: 'وسط الظهر الأيسر', bodyView: BodyView.back, position: OrganPosition(x: 0.38, y: 0.35), ), OrganModel( id: 'right_mid_back', name: 'back', + nameAr: 'الظهر', description: 'Right Mid Back', + descriptionAr: 'وسط الظهر الأيمن', bodyView: BodyView.back, position: OrganPosition(x: 0.62, y: 0.35), ), @@ -330,21 +412,27 @@ class OrganData { OrganModel( id: 'lower_spine', name: 'lower_back', + nameAr: 'أسفل_الظهر', description: 'Lower Back', + descriptionAr: 'أسفل الظهر', bodyView: BodyView.back, position: OrganPosition(x: 0.50, y: 0.43), ), OrganModel( id: 'left_lower_back', name: 'lower_back', + nameAr: 'أسفل_الظهر', description: 'Left Lower Back', + descriptionAr: 'أسفل الظهر الأيسر', bodyView: BodyView.back, position: OrganPosition(x: 0.40, y: 0.43), ), OrganModel( id: 'right_lower_back', name: 'lower_back', + nameAr: 'أسفل_الظهر', description: 'Right Lower Back', + descriptionAr: 'أسفل الظهر الأيمن', bodyView: BodyView.back, position: OrganPosition(x: 0.60, y: 0.43), ), @@ -353,21 +441,27 @@ class OrganData { OrganModel( id: 'sacrum', name: 'lower_back', + nameAr: 'أسفل_الظهر', description: 'Sacrum', + descriptionAr: 'العجز', bodyView: BodyView.back, position: OrganPosition(x: 0.50, y: 0.52), ), OrganModel( id: 'left_glute', name: 'buttocks', + nameAr: 'الأرداف', description: 'Left Glute', + descriptionAr: 'الأرداف اليسرى', bodyView: BodyView.back, position: OrganPosition(x: 0.45, y: 0.56), ), OrganModel( id: 'right_glute', name: 'buttocks', + nameAr: 'الأرداف', description: 'Right Glute', + descriptionAr: 'الأرداف اليمنى', bodyView: BodyView.back, position: OrganPosition(x: 0.55, y: 0.56), ), @@ -376,28 +470,36 @@ class OrganData { OrganModel( id: 'left_hamstring', name: 'thigh', + nameAr: 'الفخذ', description: 'Left Hamstring', + descriptionAr: 'عضلة الفخذ الخلفية اليسرى', bodyView: BodyView.back, position: OrganPosition(x: 0.43, y: 0.65), ), OrganModel( id: 'right_hamstring', name: 'thigh', + nameAr: 'الفخذ', description: 'Right Hamstring', + descriptionAr: 'عضلة الفخذ الخلفية اليمنى', bodyView: BodyView.back, position: OrganPosition(x: 0.57, y: 0.65), ), OrganModel( id: 'left_calf', name: 'lower_leg', + nameAr: 'الساق_السفلي', description: 'Left Calf', + descriptionAr: 'ربلة الساق اليسرى', bodyView: BodyView.back, position: OrganPosition(x: 0.43, y: 0.79), ), OrganModel( id: 'right_calf', name: 'lower_leg', + nameAr: 'الساق_السفلي', description: 'Right Calf', + descriptionAr: 'ربلة الساق اليمنى', bodyView: BodyView.back, position: OrganPosition(x: 0.57, y: 0.79), ), diff --git a/lib/features/symptoms_checker/models/organ_model.dart b/lib/features/symptoms_checker/models/organ_model.dart index fab7dff..9a871f3 100644 --- a/lib/features/symptoms_checker/models/organ_model.dart +++ b/lib/features/symptoms_checker/models/organ_model.dart @@ -3,29 +3,47 @@ import 'package:hmg_patient_app_new/core/enums.dart'; class OrganModel { final String id; final String name; + final String nameAr; final String description; + final String descriptionAr; final BodyView bodyView; final OrganPosition position; const OrganModel({ required this.id, required this.name, + required this.nameAr, required this.description, + required this.descriptionAr, required this.bodyView, required this.position, }); + /// Get localized name based on isArabic flag + String getLocalizedName(bool isArabic) { + return isArabic ? nameAr : name; + } + + /// Get localized description based on isArabic flag + String getLocalizedDescription(bool isArabic) { + return isArabic ? descriptionAr : description; + } + OrganModel copyWith({ String? id, String? name, + String? nameAr, String? description, + String? descriptionAr, BodyView? bodyView, OrganPosition? position, }) { return OrganModel( id: id ?? this.id, name: name ?? this.name, + nameAr: nameAr ?? this.nameAr, description: description ?? this.description, + descriptionAr: descriptionAr ?? this.descriptionAr, bodyView: bodyView ?? this.bodyView, position: position ?? this.position, ); diff --git a/lib/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart b/lib/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart index b2be4a2..9928d78 100644 --- a/lib/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart +++ b/lib/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart @@ -22,6 +22,8 @@ class RiskAndSuggestionsItemModel { final String? type; final String? name; final String? commonName; + final String? nameAr; + final String? commonNameAr; final String? language; RiskAndSuggestionsItemModel({ @@ -29,6 +31,8 @@ class RiskAndSuggestionsItemModel { this.type, this.name, this.commonName, + this.nameAr, + this.commonNameAr, this.language, }); @@ -38,6 +42,8 @@ class RiskAndSuggestionsItemModel { type: json['type'], name: json['name'], commonName: json['common_name'], + nameAr: json['nameAr'], + commonNameAr: json['common_nameAr'], language: json['language'], ); } @@ -48,12 +54,17 @@ class RiskAndSuggestionsItemModel { 'type': type, 'name': name, 'common_name': commonName, + 'nameAr': nameAr, + 'common_nameAr': commonNameAr, 'language': language, }; } - // Helper method to get display name - String getDisplayName() { + // Helper method to get display name based on locale + String getDisplayName({bool isArabic = false}) { + if (isArabic) { + return commonNameAr ?? nameAr ?? commonName ?? name ?? ''; + } return commonName ?? name ?? ''; } } diff --git a/lib/features/symptoms_checker/models/resp_models/triage_response_model.dart b/lib/features/symptoms_checker/models/resp_models/triage_response_model.dart index 60a3ad3..da0f4b6 100644 --- a/lib/features/symptoms_checker/models/resp_models/triage_response_model.dart +++ b/lib/features/symptoms_checker/models/resp_models/triage_response_model.dart @@ -172,6 +172,8 @@ class TriageCondition { final String? id; final String? name; final String? commonName; + final String? nameAr; + final String? commonNameAr; final double? probability; final ConditionDetails? conditionDetails; @@ -179,6 +181,8 @@ class TriageCondition { this.id, this.name, this.commonName, + this.nameAr, + this.commonNameAr, this.probability, this.conditionDetails, }); @@ -188,6 +192,8 @@ class TriageCondition { id: json['id'], name: json['name'], commonName: json['common_name'], + nameAr: json['nameAr'], + commonNameAr: json['common_nameAr'], probability: json['probability']?.toDouble(), conditionDetails: json['condition_details'] != null ? ConditionDetails.fromJson(json['condition_details']) : null, ); @@ -198,11 +204,21 @@ class TriageCondition { 'id': id, 'name': name, 'common_name': commonName, + 'nameAr': nameAr, + 'common_nameAr': commonNameAr, 'probability': probability, 'condition_details': conditionDetails?.toJson(), }; } + /// Get display name based on locale + String getDisplayName({bool isArabic = false}) { + if (isArabic) { + return commonNameAr ?? nameAr ?? commonName ?? name ?? ''; + } + return commonName ?? name ?? ''; + } + /// Get probability as percentage String getProbabilityPercentage() { if (probability == null) return '0%'; diff --git a/lib/features/symptoms_checker/symptoms_checker_view_model.dart b/lib/features/symptoms_checker/symptoms_checker_view_model.dart index 929fe91..ed82451 100644 --- a/lib/features/symptoms_checker/symptoms_checker_view_model.dart +++ b/lib/features/symptoms_checker/symptoms_checker_view_model.dart @@ -23,6 +23,23 @@ class SymptomsCheckerViewModel extends ChangeNotifier { required this.appState, }); + // Gender constants for both English and Arabic + static const String _maleEn = "male"; + static const String _femaleEn = "female"; + static const String _maleAr = "ذكر"; + static const String _femaleAr = "أنثى"; + + /// Convert gender string to integer (1 for male, 2 for female) + /// Handles both English and Arabic values + int _getGenderAsInt(String? gender) { + if (gender == null) return 1; // Default to male + final lowerGender = gender.toLowerCase(); + if (lowerGender == _maleEn || gender == _maleAr) { + return 1; + } + return 2; // Female or any other value + } + // State variables bool isBodyHidden = false; BodyView _currentView = BodyView.front; @@ -440,7 +457,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { await getRiskFactors( age: _selectedAge!, - gender: _selectedGender!.toLowerCase() == "male" ? 1 : 2, + gender: _getGenderAsInt(_selectedGender), evidenceIds: evidenceIds, sessionId: currentSessionId, language: appState.isArabic() ? 'ar' : 'en', @@ -498,6 +515,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier { id: "not_applicable", commonName: "Not Applicable", name: "Not Applicable", + commonNameAr: "لا ينطبق", + nameAr: "لا ينطبق", language: appState.isArabic() ? 'ar' : 'en', type: null, ); @@ -612,7 +631,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { await getSuggestions( age: _selectedAge!, - gender: _selectedGender!.toLowerCase() == "male" ? 1 : 2, + gender: _getGenderAsInt(_selectedGender), evidenceIds: evidenceIds, language: appState.isArabic() ? 'ar' : 'en', onSuccess: (response) { @@ -668,6 +687,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier { id: "not_applicable", commonName: "Not Applicable", name: "Not Applicable", + commonNameAr: "لا ينطبق", + nameAr: "لا ينطبق", language: appState.isArabic() ? 'ar' : 'en', type: null, ); @@ -764,7 +785,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { await getDiagnosisForTriage( age: _selectedAge!, - gender: _selectedGender!.toLowerCase() == "male" ? 1 : 2, + gender: _getGenderAsInt(_selectedGender), evidenceIds: evidenceIds, language: appState.isArabic() ? 'ar' : 'en', onSuccess: (response) { @@ -927,6 +948,22 @@ class SymptomsCheckerViewModel extends ChangeNotifier { bool get isUserInfoFirstPage => _userInfoCurrentPage == 0; + /// Check if current user info page has valid selection + bool get isCurrentUserInfoPageValid { + switch (_userInfoCurrentPage) { + case 0: // Gender page + return _selectedGender != null && _selectedGender!.isNotEmpty; + case 1: // Age/Birth date page + return _dateOfBirth != null || (_selectedAge != null && _selectedAge! > 0); + case 2: // Height page + return _selectedHeight > 0; + case 3: // Weight page + return _selectedWeight > 0; + default: + return false; + } + } + /// Validate and submit user info Map getUserInfoData() { return { @@ -988,7 +1025,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { final result = await symptomsCheckerRepo.getBodySymptomsByName( organNames: organNames, userSessionToken: currentSessionAuthToken, - gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2, + gender: _getGenderAsInt(selectedGender ?? "Male"), ); result.fold( diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index d6f2d23..41cb64f 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -1371,6 +1371,17 @@ abstract class LocaleKeys { static const suggestions = 'suggestions'; static const pleaseGoBackAndSelectOrgansFirst = 'pleaseGoBackAndSelectOrgansFirst'; static const symptomsSelector = 'symptomsSelector'; + static const possibleSymptomsRelatedTo = 'possibleSymptomsRelatedTo'; + static const suggestionAIListDescription = 'suggestionAIListDescription'; + static const analyzing = 'analyzing'; + static const seekMedicalAdvice = 'seekMedicalAdvice'; + static const monitorDescription = 'monitorDescription'; + static const seekMedicalAdviceDescription = 'seekMedicalAdviceDescription'; + static const emergencyDescription = 'emergencyDescription'; + static const colorScience = 'colorScience'; + static const noNeedToVisitDoctor = 'noNeedToVisitDoctor'; + static const commonSymptom = 'commonSymptom'; + static const notApplicable = 'notApplicable'; static const emergencyTriage = 'emergencyTriage'; static const emergencyEvidenceDetected = 'emergencyEvidenceDetected'; static const noQuestionItemsAvailable = 'noQuestionItemsAvailable'; diff --git a/lib/presentation/symptoms_checker/organ_selector_screen.dart b/lib/presentation/symptoms_checker/organ_selector_screen.dart index cc956dc..e3944c2 100644 --- a/lib/presentation/symptoms_checker/organ_selector_screen.dart +++ b/lib/presentation/symptoms_checker/organ_selector_screen.dart @@ -7,7 +7,6 @@ import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; -import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; @@ -44,7 +43,9 @@ class _OrganSelectorPageState extends State { ); return; } - LoaderBottomSheet.showLoader(loadingText: LocaleKeys.pleaseWait.tr(context: context),); + LoaderBottomSheet.showLoader( + loadingText: LocaleKeys.pleaseWait.tr(context: context), + ); final String userName = 'guest_user'; final String password = '123456'; @@ -102,7 +103,7 @@ class _OrganSelectorPageState extends State { width: 32.h, height: 32.h, ), - padding: const EdgeInsets.only(left: 12), + padding: EdgeInsetsDirectional.only(start: 12), onPressed: () => Navigator.pop(context), highlightColor: Colors.transparent, ), @@ -125,21 +126,32 @@ class _OrganSelectorPageState extends State { } Widget _buildBodyViewer(SymptomsCheckerViewModel viewModel) { - return Padding( - padding: EdgeInsets.fromLTRB(16.h, 16.h, 16.h, 60.h), - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 600), - transitionBuilder: (child, animation) => _build3DFlipTransition(child, animation), - switchInCurve: Curves.easeInOut, - switchOutCurve: Curves.easeInOut, - child: InteractiveBodyWidget( - key: ValueKey(viewModel.currentView), - bodyImageAsset: viewModel.currentView == BodyView.front ? AppAssets.fullBodyFront : AppAssets.fullBodyBack, - organs: viewModel.currentOrgans, - selectedOrganIds: viewModel.selectedOrganIds, - onOrganTap: viewModel.toggleOrganSelection, - isBodyHidden: viewModel.isBodyHidden, - tooltipOrganId: viewModel.tooltipOrganId, + return GestureDetector( + onHorizontalDragEnd: (details) { + // Swipe left or right to toggle view + if (details.primaryVelocity != null) { + if (details.primaryVelocity! < -200 || details.primaryVelocity! > 200) { + viewModel.toggleView(); + } + } + }, + child: Padding( + padding: EdgeInsets.fromLTRB(16.h, 16.h, 16.h, 60.h), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 600), + transitionBuilder: (child, animation) => _build3DFlipTransition(child, animation), + switchInCurve: Curves.easeInOut, + switchOutCurve: Curves.easeInOut, + child: InteractiveBodyWidget( + key: ValueKey(viewModel.currentView), + bodyImageAsset: viewModel.currentView == BodyView.front ? AppAssets.fullBodyFront : AppAssets.fullBodyBack, + organs: viewModel.currentOrgans, + selectedOrganIds: viewModel.selectedOrganIds, + onOrganTap: viewModel.toggleOrganSelection, + isBodyHidden: viewModel.isBodyHidden, + tooltipOrganId: viewModel.tooltipOrganId, + isArabic: _appState.isArabic(), + ), ), ), ); @@ -169,8 +181,8 @@ class _OrganSelectorPageState extends State { } Widget _buildViewToggleButtons(SymptomsCheckerViewModel viewModel) { - return Positioned( - left: 16.w, + return PositionedDirectional( + start: 16.w, top: 36.h, child: Column( children: [ @@ -275,6 +287,8 @@ class _OrganSelectorPageState extends State { ); } + final isArabic = _appState.isArabic(); + return ConstrainedBox( constraints: BoxConstraints(maxHeight: 150.h), child: SingleChildScrollView( @@ -284,7 +298,7 @@ class _OrganSelectorPageState extends State { runSpacing: 8.h, children: viewModel.selectedOrgans.map((organ) { return AppCustomChipWidget( - labelText: organ.description, + labelText: organ.getLocalizedDescription(isArabic), backgroundColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, deleteIcon: AppAssets.cancel, @@ -313,8 +327,8 @@ class _OrganSelectorPageState extends State { } Widget _buildExpandCollapseButton(SymptomsCheckerViewModel viewModel) { - return Positioned( - right: 24.w, + return PositionedDirectional( + end: 24.w, top: -24.h, child: GestureDetector( onTap: viewModel.toggleBottomSheet, @@ -331,13 +345,16 @@ class _OrganSelectorPageState extends State { borderRadius: 11.r, ), child: Center( - child: AnimatedRotation( - duration: const Duration(milliseconds: 300), - turns: viewModel.isBottomSheetExpanded ? 0.25 : -0.25, - child: Utils.buildSvgWithAssets( - icon: AppAssets.arrowRight, - width: 25.w, - height: 25.h, + child: Transform.flip( + flipX: _appState.isArabic(), + child: AnimatedRotation( + duration: const Duration(milliseconds: 300), + turns: viewModel.isBottomSheetExpanded ? 0.25 : -0.25, + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrowRight, + width: 25.w, + height: 25.h, + ), ), ), ), diff --git a/lib/presentation/symptoms_checker/possible_conditions_screen.dart b/lib/presentation/symptoms_checker/possible_conditions_screen.dart index 858218d..8fb0e0f 100644 --- a/lib/presentation/symptoms_checker/possible_conditions_screen.dart +++ b/lib/presentation/symptoms_checker/possible_conditions_screen.dart @@ -108,25 +108,25 @@ class PossibleConditionsPage extends StatelessWidget { ); } - Widget _buildInfoTile({required PossibleConditionsSeverityEnum severityEnum}) { + Widget _buildInfoTile({required PossibleConditionsSeverityEnum severityEnum, required BuildContext context}) { String title; String description; Color dotColor; switch (severityEnum) { case PossibleConditionsSeverityEnum.monitorOnly: - title = "Monitor".needTranslation; - description = "No need to seek medical advice. Just keep healthy routine.".needTranslation; + title = LocaleKeys.monitor.tr(context: context); + description = LocaleKeys.monitorDescription.tr(context: context); dotColor = AppColors.chipColorMonitor; break; case PossibleConditionsSeverityEnum.seekMedicalAdvice: - title = "Seek Medical Advice".needTranslation; - description = "Not emergency but better to monitor the symptoms.".needTranslation; + title = LocaleKeys.seekMedicalAdvice.tr(context: context); + description = LocaleKeys.seekMedicalAdviceDescription.tr(context: context); dotColor = AppColors.chipColorSeekMedicalAdvice; break; case PossibleConditionsSeverityEnum.emergency: - title = "Emergency".needTranslation; - description = "Need to consult doctor as soon as possible before getting too late.".needTranslation; + title = LocaleKeys.emergency.tr(context: context); + description = LocaleKeys.emergencyDescription.tr(context: context); dotColor = AppColors.chipColorEmergency; break; } @@ -156,7 +156,7 @@ class PossibleConditionsPage extends StatelessWidget { _buildSeverityDetailsBottomsheet({required BuildContext context}) { return showCommonBottomSheetWithoutHeight( - title: "Color Science".needTranslation, + title: LocaleKeys.colorScience.tr(context: context), context, child: Container( padding: EdgeInsets.all(16.w), @@ -164,11 +164,11 @@ class PossibleConditionsPage extends StatelessWidget { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true), child: Column( children: [ - _buildInfoTile(severityEnum: PossibleConditionsSeverityEnum.monitorOnly), + _buildInfoTile(severityEnum: PossibleConditionsSeverityEnum.monitorOnly, context: context), Divider(color: AppColors.bottomNAVBorder, height: 24.h, thickness: 1), - _buildInfoTile(severityEnum: PossibleConditionsSeverityEnum.seekMedicalAdvice), + _buildInfoTile(severityEnum: PossibleConditionsSeverityEnum.seekMedicalAdvice, context: context), Divider(color: AppColors.bottomNAVBorder, height: 24.h, thickness: 1), - _buildInfoTile(severityEnum: PossibleConditionsSeverityEnum.emergency), + _buildInfoTile(severityEnum: PossibleConditionsSeverityEnum.emergency, context: context), ], ), ), diff --git a/lib/presentation/symptoms_checker/risk_factors_screen.dart b/lib/presentation/symptoms_checker/risk_factors_screen.dart index da41d78..8264b8b 100644 --- a/lib/presentation/symptoms_checker/risk_factors_screen.dart +++ b/lib/presentation/symptoms_checker/risk_factors_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; @@ -25,11 +26,13 @@ class RiskFactorsScreen extends StatefulWidget { class _RiskFactorsScreenState extends State { late DialogService dialogService; + late AppState _appState; @override void initState() { super.initState(); dialogService = getIt(); + _appState = getIt(); // Fetch risk factors based on selected symptoms WidgetsBinding.instance.addPostFrameCallback((_) { final viewModel = context.read(); @@ -90,6 +93,8 @@ class _RiskFactorsScreenState extends State { } Widget _buildRiskFactorsList(SymptomsCheckerViewModel viewModel) { + final isArabic = _appState.isArabic(); + return Container( key: ValueKey(viewModel.riskFactorsList.length), width: double.infinity, @@ -100,7 +105,7 @@ class _RiskFactorsScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ ...viewModel.riskFactorsList.map((factor) { - return _buildRiskFactorItem(viewModel, factor.id ?? '', factor.getDisplayName()); + return _buildRiskFactorItem(viewModel, factor.id ?? '', factor.getDisplayName(isArabic: isArabic)); }), SizedBox(height: 12.w), Row( diff --git a/lib/presentation/symptoms_checker/suggestions_screen.dart b/lib/presentation/symptoms_checker/suggestions_screen.dart index f09ebd5..8d32b7f 100644 --- a/lib/presentation/symptoms_checker/suggestions_screen.dart +++ b/lib/presentation/symptoms_checker/suggestions_screen.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; @@ -24,11 +25,13 @@ class SuggestionsScreen extends StatefulWidget { class _SuggestionsScreenState extends State { late DialogService dialogService; + late AppState _appState; @override void initState() { super.initState(); dialogService = getIt(); + _appState = getIt(); // Initialize symptom groups based on selected organs WidgetsBinding.instance.addPostFrameCallback((_) { final viewModel = context.read(); @@ -92,6 +95,8 @@ class _SuggestionsScreenState extends State { } Widget _buildSuggestionsList(SymptomsCheckerViewModel viewModel) { + final isArabic = _appState.isArabic(); + return Container( key: ValueKey(viewModel.suggestionsList.length), width: double.infinity, @@ -102,7 +107,7 @@ class _SuggestionsScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ ...viewModel.suggestionsList.map((factor) { - return _buildSuggestionItem(viewModel, factor.id ?? '', factor.getDisplayName()); + return _buildSuggestionItem(viewModel, factor.id ?? '', factor.getDisplayName(isArabic: isArabic)); }), SizedBox(height: 12.w), Row( @@ -115,7 +120,7 @@ class _SuggestionsScreenState extends State { ), SizedBox(width: 12.w), Expanded( - child: "This is a list of symptoms suggested by our AI, based on the information gathered so far during the interview".toText12( + child: LocaleKeys.suggestionAIListDescription.tr(context: context).toText12( color: AppColors.greyInfoTextColor, ), ) diff --git a/lib/presentation/symptoms_checker/symptoms_selector_screen.dart b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart index b241b44..2bfcb62 100644 --- a/lib/presentation/symptoms_checker/symptoms_selector_screen.dart +++ b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; @@ -26,11 +27,13 @@ class SymptomsSelectorPage extends StatefulWidget { class _SymptomsSelectorPageState extends State { late DialogService dialogService; + late AppState _appState; @override void initState() { super.initState(); dialogService = getIt(); + _appState = getIt(); // Initialize symptom groups based on selected organs WidgetsBinding.instance.addPostFrameCallback((_) { final viewModel = context.read(); @@ -100,10 +103,11 @@ class _SymptomsSelectorPageState extends State { // Find matching organ ID from selected organs String? organId; String? organName; + final isArabic = _appState.isArabic(); for (var organ in viewModel.selectedOrgans) { if (organ.name == organResult.name) { organId = organ.id; - organName = organ.description; + organName = organ.getLocalizedDescription(isArabic); break; } } @@ -141,6 +145,8 @@ class _SymptomsSelectorPageState extends State { String organName, List symptoms, ) { + final isArabic = _appState.isArabic(); + return Container( width: double.infinity, margin: EdgeInsets.symmetric(horizontal: 24.w), @@ -154,7 +160,7 @@ class _SymptomsSelectorPageState extends State { children: [ Expanded( child: Text( - 'Possible symptoms related to "$organName"', + '${LocaleKeys.possibleSymptomsRelatedTo.tr(context: context)} "$organName"', style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, color: AppColors.textColor), ), ), @@ -166,7 +172,7 @@ class _SymptomsSelectorPageState extends State { spacing: 8.w, children: symptoms.map((symptom) { final symptomId = symptom.id ?? ''; - final symptomLabel = symptom.getDisplayName(false); // Pass isArabic from AppState if needed + final symptomLabel = symptom.getDisplayName(isArabic); bool isSelected = viewModel.isSymptomSelected(organId, symptomId); return GestureDetector( onTap: () => viewModel.toggleSymptomSelection(organId, symptomId), @@ -306,7 +312,7 @@ class _SymptomsSelectorPageState extends State { fontSize: 16.f, ), ), - ], + ], ), SizedBox(height: 24.h), ], diff --git a/lib/presentation/symptoms_checker/triage_screen.dart b/lib/presentation/symptoms_checker/triage_screen.dart index c0d48d6..a7975e2 100644 --- a/lib/presentation/symptoms_checker/triage_screen.dart +++ b/lib/presentation/symptoms_checker/triage_screen.dart @@ -4,6 +4,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/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'; @@ -32,12 +33,14 @@ class TriagePage extends StatefulWidget { class _TriagePageState extends State { late SymptomsCheckerViewModel viewModel; late DialogService dialogService; + late AppState _appState; @override void initState() { super.initState(); viewModel = context.read(); dialogService = getIt.get(); + _appState = getIt.get(); // Start triage process when screen loads WidgetsBinding.instance.addPostFrameCallback((_) { @@ -436,7 +439,8 @@ class _TriagePageState extends State { Widget _buildStickyBottomCard(BuildContext context, SymptomsCheckerViewModel viewModel) { // Get the top condition with highest probability final conditions = viewModel.currentConditions ?? []; - String suggestedCondition = "Analyzing..."; + final isArabic = _appState.isArabic(); + String suggestedCondition = LocaleKeys.analyzing.tr(context: context); double probability = 0.0; if (conditions.isNotEmpty) { @@ -445,7 +449,7 @@ class _TriagePageState extends State { sortedConditions.sort((a, b) => (b.probability ?? 0.0).compareTo(a.probability ?? 0.0)); final topCondition = sortedConditions.first; - suggestedCondition = topCondition.commonName ?? topCondition.name ?? "Unknown"; + suggestedCondition = topCondition.getDisplayName(isArabic: isArabic); probability = (topCondition.probability ?? 0.0) * 100; // Convert to percentage } // final bool isHighConfidence = probability >= 70.0; diff --git a/lib/presentation/symptoms_checker/user_info_selection.dart b/lib/presentation/symptoms_checker/user_info_selection.dart index 78eb916..1d05c5a 100644 --- a/lib/presentation/symptoms_checker/user_info_selection.dart +++ b/lib/presentation/symptoms_checker/user_info_selection.dart @@ -43,13 +43,10 @@ class _UserInfoSelectionPageState extends State { if (user == null) return; // Populate gender (gender is int: 1=Male, 2=Female) + // Use internal keys (male/female) for storage if (user.gender != null) { - String genderStr = user.gender == 1 - ? "Male" - : user.gender == 2 - ? "Female" - : "Other"; - viewModel.setGender(genderStr); + String genderKey = user.gender == 1 ? "male" : user.gender == 2 ? "female" : "other"; + viewModel.setGender(genderKey); } if (user.dateofBirth != null && user.dateofBirth!.isNotEmpty) { @@ -84,12 +81,13 @@ class _UserInfoSelectionPageState extends State { child: Row( children: [ Container( - height: 40.h, - width: 40.h, - margin: EdgeInsets.only(right: 10.h), - padding: EdgeInsets.all(8.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 12.r, color: AppColors.greyColor), - child: Utils.buildSvgWithAssets(icon: leadingIcon, iconColor: iconColor)), + height: 40.h, + width: 40.h, + margin: EdgeInsetsDirectional.only(end: 10.h), + padding: EdgeInsets.all(8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 12.r, color: AppColors.greyColor), + child: Utils.buildSvgWithAssets(icon: leadingIcon, iconColor: iconColor), + ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -112,6 +110,20 @@ class _UserInfoSelectionPageState extends State { ).paddingSymmetrical(0, 8.h); } + /// Convert internal gender key to localized display string + String _getLocalizedGender(String? genderKey, BuildContext context) { + if (genderKey == null) return LocaleKeys.notSet.tr(context: context); + + final lowerKey = genderKey.toLowerCase(); + if (lowerKey == "male") { + return LocaleKeys.malE.tr(context: context); + } else if (lowerKey == "female") { + return LocaleKeys.femaleGender.tr(context: context); + } else { + return genderKey; // Return as-is if not recognized + } + } + @override Widget build(BuildContext context) { AppState appState = getIt.get(); @@ -151,7 +163,7 @@ class _UserInfoSelectionPageState extends State { viewModel.selectedWeight == null; // Get display values - String genderText = viewModel.selectedGender ?? LocaleKeys.notSet.tr(context: context); + String genderText = _getLocalizedGender(viewModel.selectedGender, context); // Show age calculated from DOB (prefer viewModel's age, fallback to calculated from user's DOB) int? displayAge = viewModel.selectedAge ?? userAgeFromDOB; String ageText = displayAge != null ? "$displayAge ${LocaleKeys.years.tr(context: context)}" : LocaleKeys.notSet.tr(context: context); diff --git a/lib/presentation/symptoms_checker/user_info_selection/pages/gender_selection_page.dart b/lib/presentation/symptoms_checker/user_info_selection/pages/gender_selection_page.dart index 6eaae8c..07f44f1 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/pages/gender_selection_page.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/pages/gender_selection_page.dart @@ -15,12 +15,16 @@ class GenderSelectionPage extends StatelessWidget { final String? selectedGender; final Function(String) onGenderSelected; - GenderSelectionPage({ + const GenderSelectionPage({ super.key, required this.selectedGender, required this.onGenderSelected, }); + // Internal gender keys (constant, not localized) + static const String maleKey = "male"; + static const String femaleKey = "female"; + _buildGenderOption(String iconPng, String label, bool isSelected) { return Container( height: 160.h, @@ -40,7 +44,27 @@ class GenderSelectionPage extends StatelessWidget { ); } - final genders = ["Male", "Female"]; + /// Check if the selected gender matches the given key + /// Handles both internal keys and localized strings + bool _isGenderSelected(String genderKey, String? currentSelection, BuildContext context) { + if (currentSelection == null) return false; + + final lowerSelection = currentSelection.toLowerCase(); + + if (genderKey == maleKey) { + // Check against internal key, English, and Arabic + return lowerSelection == maleKey || + lowerSelection == "male" || + currentSelection == LocaleKeys.malE.tr(context: context) || + currentSelection == "ذكر"; + } else { + // Check against internal key, English, and Arabic + return lowerSelection == femaleKey || + lowerSelection == "female" || + currentSelection == LocaleKeys.femaleGender.tr(context: context) || + currentSelection == "أنثى"; + } + } @override Widget build(BuildContext context) { @@ -56,16 +80,25 @@ class GenderSelectionPage extends StatelessWidget { children: [ Expanded( child: InkWell( - onTap: () => onGenderSelected(genders[0]), - child: _buildGenderOption(AppAssets.maleIcon, LocaleKeys.malE.tr(context: context), symptomsViewModel.selectedGender == genders[0]), + onTap: () => onGenderSelected(maleKey), + child: _buildGenderOption( + AppAssets.maleIcon, + LocaleKeys.malE.tr(context: context), + _isGenderSelected(maleKey, symptomsViewModel.selectedGender, context), + ), ), ), SizedBox(width: 16.w), Expanded( - child: InkWell( - onTap: () => onGenderSelected(genders[1]), - child: _buildGenderOption(AppAssets.femaleIcon, LocaleKeys.femaleGender.tr(context: context), symptomsViewModel.selectedGender == genders[1]), - )) + child: InkWell( + onTap: () => onGenderSelected(femaleKey), + child: _buildGenderOption( + AppAssets.femaleIcon, + LocaleKeys.femaleGender.tr(context: context), + _isGenderSelected(femaleKey, symptomsViewModel.selectedGender, context), + ), + ), + ) ], ), ], diff --git a/lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart b/lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart index b5df05b..fdfe228 100644 --- a/lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart +++ b/lib/presentation/symptoms_checker/user_info_selection/user_info_flow_manager.dart @@ -4,7 +4,6 @@ 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/route_extensions.dart'; -import 'package:hmg_patient_app_new/extensions/string_extensions.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/user_info_selection/pages/age_selection_page.dart'; @@ -29,13 +28,15 @@ class _UserInfoFlowManagerState extends State { late PageController _pageController; late SymptomsCheckerViewModel _viewModel; - // Page titles - final List _pageTitles = [ - "Your Gender", - "Your Birth Date", - "Your Height", - "Your Weight", - ]; + // Get localized page titles + List _getPageTitles(BuildContext context) { + return [ + LocaleKeys.genderSymptoms.tr(context: context), + LocaleKeys.dateOfBirthSymptoms.tr(context: context), + LocaleKeys.heightSymptoms.tr(context: context), + LocaleKeys.weightSymptoms.tr(context: context), + ]; + } @override void initState() { @@ -187,9 +188,11 @@ class _UserInfoFlowManagerState extends State { ? // Single page edit mode - show only Save button CustomButton( text: LocaleKeys.save.tr(context: context), - onPressed: _onNext, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, + isDisabled: !(viewModel.isCurrentUserInfoPageValid), + onPressed: () => viewModel.isCurrentUserInfoPageValid ? _onNext() : () {}, + backgroundColor: + viewModel.isCurrentUserInfoPageValid ? AppColors.primaryRedColor : AppColors.primaryRedColor.withValues(alpha: 0.5), + borderColor: viewModel.isCurrentUserInfoPageValid ? AppColors.primaryRedColor : AppColors.primaryRedColor.withValues(alpha: 0.5), textColor: AppColors.whiteColor, fontSize: 16.f, ) @@ -228,6 +231,8 @@ class _UserInfoFlowManagerState extends State { @override Widget build(BuildContext context) { + final pageTitles = _getPageTitles(context); + return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Column( @@ -235,7 +240,7 @@ class _UserInfoFlowManagerState extends State { Expanded( child: CollapsingListView( physics: NeverScrollableScrollPhysics(), - title: _pageTitles[_viewModel.userInfoCurrentPage], + title: pageTitles[_viewModel.userInfoCurrentPage], isLeading: true, child: Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/presentation/symptoms_checker/widgets/condition_card.dart b/lib/presentation/symptoms_checker/widgets/condition_card.dart index b95e55c..e48f41e 100644 --- a/lib/presentation/symptoms_checker/widgets/condition_card.dart +++ b/lib/presentation/symptoms_checker/widgets/condition_card.dart @@ -2,6 +2,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; @@ -24,8 +26,11 @@ class ConditionCard extends StatelessWidget { this.onActionPressed, }); - /// Get title from condition - String get title => condition.commonName ?? condition.name ?? ''; + /// Get title from condition (localized) + String get title { + final appState = getIt(); + return condition.getDisplayName(isArabic: appState.isArabic()); + } /// Get percentage from probability (0-1 to 0-100) int get percentage => ((condition.probability ?? 0) * 100).toInt(); @@ -69,14 +74,14 @@ class ConditionCard extends StatelessWidget { return LocaleKeys.emergency.tr(context: context); case TriageLevelEnum.consultation24: case TriageLevelEnum.consultation: - return "Seek Medical Advice".needTranslation; + return LocaleKeys.seekMedicalAdvice.tr(context: context); case TriageLevelEnum.selfCare: return LocaleKeys.monitor.tr(context: context); } } // Default based on probability if (percentage >= 70) { - return "Seek Medical Advice".needTranslation; + return LocaleKeys.seekMedicalAdvice.tr(context: context); } else { return LocaleKeys.monitor.tr(context: context); } @@ -85,7 +90,7 @@ class ConditionCard extends StatelessWidget { /// Get monitor note if applicable String? getMonitorNote(BuildContext context) { if (severityEnum == PossibleConditionsSeverityEnum.monitorOnly) { - return "No need to visit doctor".needTranslation; + return LocaleKeys.noNeedToVisitDoctor.tr(context: context); } return null; } @@ -236,7 +241,7 @@ class ConditionCard extends StatelessWidget { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Common Symptom".needTranslation.toText14(weight: FontWeight.w600, color: AppColors.textColor), + LocaleKeys.commonSymptom.tr(context: context).toText14(weight: FontWeight.w600, color: AppColors.textColor), _buildSymptomsRow(), SizedBox(height: 16.h), LocaleKeys.description.tr(context: context).toText14(weight: FontWeight.bold, color: AppColors.textColor), @@ -244,7 +249,7 @@ class ConditionCard extends StatelessWidget { description.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), if (severityEnum != PossibleConditionsSeverityEnum.monitorOnly) CustomButton( - text: "Book Appointment".needTranslation, + text: LocaleKeys.bookAppointment.tr(context: context), onPressed: () { if (onActionPressed != null) { onActionPressed!(); diff --git a/lib/presentation/symptoms_checker/widgets/interactive_body_widget.dart b/lib/presentation/symptoms_checker/widgets/interactive_body_widget.dart index 8957fb1..62c38f9 100644 --- a/lib/presentation/symptoms_checker/widgets/interactive_body_widget.dart +++ b/lib/presentation/symptoms_checker/widgets/interactive_body_widget.dart @@ -13,6 +13,7 @@ class InteractiveBodyWidget extends StatefulWidget { final bool isBodyHidden; final Function(String organId) onOrganTap; final String? tooltipOrganId; + final bool isArabic; const InteractiveBodyWidget({ super.key, @@ -22,6 +23,7 @@ class InteractiveBodyWidget extends StatefulWidget { required this.onOrganTap, this.isBodyHidden = false, this.tooltipOrganId, + this.isArabic = false, }); @override @@ -118,7 +120,10 @@ class _InteractiveBodyWidgetState extends State { (organ) { final centerX = organ.position.x * imageConstraints.maxWidth; final centerY = organ.position.y * imageConstraints.maxHeight; - return OrganTooltipWidget(organName: organ.description, position: Offset(centerX, centerY)); + return OrganTooltipWidget( + organName: organ.getLocalizedDescription(widget.isArabic), + position: Offset(centerX, centerY), + ); }, ), ], diff --git a/lib/widgets/buttons/custom_button.dart b/lib/widgets/buttons/custom_button.dart index 7fa5c3f..a81f0eb 100644 --- a/lib/widgets/buttons/custom_button.dart +++ b/lib/widgets/buttons/custom_button.dart @@ -74,15 +74,18 @@ class CustomButton extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ if (icon != null) - Padding( - padding: text.isNotEmpty ? EdgeInsets.only(right: 4.w, left: 4.w) : EdgeInsets.zero, - child: Utils.buildSvgWithAssets(icon: icon!, iconColor: iconColor, isDisabled: isDisabled, width: iconS, height: iconS), + Flexible( + child: Padding( + padding: text.isNotEmpty ? EdgeInsets.only(right: 4.w, left: 4.w) : EdgeInsets.zero, + child: Utils.buildSvgWithAssets(icon: icon!, iconColor: iconColor, isDisabled: isDisabled, width: iconS, height: iconS), + ), ), - Visibility( - visible: text.isNotEmpty, - child: Text( + if (text.isNotEmpty) + Text( text, - overflow: textOverflow, + overflow: textOverflow ?? TextOverflow.ellipsis, + maxLines: 1, + textAlign: TextAlign.center, style: context.dynamicTextStyle( fontSize: fontS, color: isDisabled ? AppColors.greyTextColor : textColor, @@ -90,7 +93,6 @@ class CustomButton extends StatelessWidget { fontWeight: fontWeight, ), ), - ), ], ), ));