Merge pull request 'Added Arabic Support for symptoms checker (faiz_dev)' (#165) from faiz_dev into master

Reviewed-on: https://34.17.182.140/Haroon6138/HMG_Patient_App_New/pulls/165
pull/168/head
Haroon6138 3 days ago
commit cf7ff4ce1d

@ -1377,6 +1377,18 @@
"suggestions": "الاقتراحات",
"pleaseGoBackAndSelectOrgansFirst": "يرجى العودة واختيار الأعضاء أولاً",
"symptomsSelector": "محدد الأعراض",
"possibleSymptomsRelatedTo": "الأعراض المحتملة المتعلقة بـ",
"suggestionAIListDescription": "هذه قائمة بالأعراض المقترحة من قبل الذكاء الاصطناعي، بناءً على المعلومات التي تم جمعها حتى الآن خلال المقابلة",
"analyzing": "جاري التحليل...",
"seekMedicalAdvice": "اطلب المشورة الطبية",
"monitorDescription": "لا حاجة لطلب المشورة الطبية. فقط حافظ على روتين صحي.",
"seekMedicalAdviceDescription": "ليست حالة طوارئ ولكن من الأفضل مراقبة الأعراض.",
"emergencyDescription": "تحتاج إلى استشارة الطبيب في أقرب وقت ممكن قبل فوات الأوان.",
"colorScience": "دلالات الألوان",
"noNeedToVisitDoctor": "لا حاجة لزي<D8B2><D98A>رة الطبيب",
"commonSymptom": "الأعراض الشائعة",
"bookAppointment": "احجز موعد",
"notApplicable": "لا ينطبق",
"emergencyTriage": "طوارئ",
"emergencyEvidenceDetected": "تم اكتشاف دليل طوارئ. يرجى طلب العناية الطبية.",
"noQuestionItemsAvailable": "لا توجد عناصر أسئلة متاحة",

@ -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",

@ -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<String, dynamic> 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<dynamic> 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<String, dynamic> 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<dynamic> 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}');
// }
//

@ -189,7 +189,7 @@ class ApiClientImp implements ApiClient {
// body['SessionID'] = "45786230487560q";
}
body['IsNewFlutterApp'] = true;
// body['IsNewFlutterApp'] = true;
body.removeWhere((key, value) => value == null);
}

@ -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),
),

@ -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,
);

@ -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 ?? '';
}
}

@ -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%';

@ -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<String, dynamic> 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(

@ -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';

@ -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<OrganSelectorPage> {
);
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<OrganSelectorPage> {
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<OrganSelectorPage> {
}
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<BodyView>(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<BodyView>(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<OrganSelectorPage> {
}
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<OrganSelectorPage> {
);
}
final isArabic = _appState.isArabic();
return ConstrainedBox(
constraints: BoxConstraints(maxHeight: 150.h),
child: SingleChildScrollView(
@ -284,7 +298,7 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
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<OrganSelectorPage> {
}
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<OrganSelectorPage> {
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,
),
),
),
),

@ -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),
],
),
),

@ -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<RiskFactorsScreen> {
late DialogService dialogService;
late AppState _appState;
@override
void initState() {
super.initState();
dialogService = getIt<DialogService>();
_appState = getIt<AppState>();
// Fetch risk factors based on selected symptoms
WidgetsBinding.instance.addPostFrameCallback((_) {
final viewModel = context.read<SymptomsCheckerViewModel>();
@ -90,6 +93,8 @@ class _RiskFactorsScreenState extends State<RiskFactorsScreen> {
}
Widget _buildRiskFactorsList(SymptomsCheckerViewModel viewModel) {
final isArabic = _appState.isArabic();
return Container(
key: ValueKey<int>(viewModel.riskFactorsList.length),
width: double.infinity,
@ -100,7 +105,7 @@ class _RiskFactorsScreenState extends State<RiskFactorsScreen> {
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(

@ -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<SuggestionsScreen> {
late DialogService dialogService;
late AppState _appState;
@override
void initState() {
super.initState();
dialogService = getIt<DialogService>();
_appState = getIt<AppState>();
// Initialize symptom groups based on selected organs
WidgetsBinding.instance.addPostFrameCallback((_) {
final viewModel = context.read<SymptomsCheckerViewModel>();
@ -92,6 +95,8 @@ class _SuggestionsScreenState extends State<SuggestionsScreen> {
}
Widget _buildSuggestionsList(SymptomsCheckerViewModel viewModel) {
final isArabic = _appState.isArabic();
return Container(
key: ValueKey<int>(viewModel.suggestionsList.length),
width: double.infinity,
@ -102,7 +107,7 @@ class _SuggestionsScreenState extends State<SuggestionsScreen> {
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<SuggestionsScreen> {
),
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,
),
)

@ -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<SymptomsSelectorPage> {
late DialogService dialogService;
late AppState _appState;
@override
void initState() {
super.initState();
dialogService = getIt<DialogService>();
_appState = getIt<AppState>();
// Initialize symptom groups based on selected organs
WidgetsBinding.instance.addPostFrameCallback((_) {
final viewModel = context.read<SymptomsCheckerViewModel>();
@ -100,10 +103,11 @@ class _SymptomsSelectorPageState extends State<SymptomsSelectorPage> {
// 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<SymptomsSelectorPage> {
String organName,
List<BodySymptom> symptoms,
) {
final isArabic = _appState.isArabic();
return Container(
width: double.infinity,
margin: EdgeInsets.symmetric(horizontal: 24.w),
@ -154,7 +160,7 @@ class _SymptomsSelectorPageState extends State<SymptomsSelectorPage> {
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<SymptomsSelectorPage> {
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<SymptomsSelectorPage> {
fontSize: 16.f,
),
),
],
],
),
SizedBox(height: 24.h),
],

@ -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<TriagePage> {
late SymptomsCheckerViewModel viewModel;
late DialogService dialogService;
late AppState _appState;
@override
void initState() {
super.initState();
viewModel = context.read<SymptomsCheckerViewModel>();
dialogService = getIt.get<DialogService>();
_appState = getIt.get<AppState>();
// Start triage process when screen loads
WidgetsBinding.instance.addPostFrameCallback((_) {
@ -436,7 +439,8 @@ class _TriagePageState extends State<TriagePage> {
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<TriagePage> {
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;

@ -43,13 +43,10 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
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<UserInfoSelectionPage> {
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<UserInfoSelectionPage> {
).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<AppState>();
@ -151,7 +163,7 @@ class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
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);

@ -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),
),
),
)
],
),
],

@ -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<UserInfoFlowManager> {
late PageController _pageController;
late SymptomsCheckerViewModel _viewModel;
// Page titles
final List<String> _pageTitles = [
"Your Gender",
"Your Birth Date",
"Your Height",
"Your Weight",
];
// Get localized page titles
List<String> _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<UserInfoFlowManager> {
? // 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<UserInfoFlowManager> {
@override
Widget build(BuildContext context) {
final pageTitles = _getPageTitles(context);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Column(
@ -235,7 +240,7 @@ class _UserInfoFlowManagerState extends State<UserInfoFlowManager> {
Expanded(
child: CollapsingListView(
physics: NeverScrollableScrollPhysics(),
title: _pageTitles[_viewModel.userInfoCurrentPage],
title: pageTitles[_viewModel.userInfoCurrentPage],
isLeading: true,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,

@ -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<AppState>();
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!();

@ -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<InteractiveBodyWidget> {
(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),
);
},
),
],

@ -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,
),
),
),
],
),
));

Loading…
Cancel
Save