diff --git a/lib/features/symptoms_checker/data/symptoms_mapping_data.dart b/lib/features/symptoms_checker/data/symptoms_mapping_data.dart new file mode 100644 index 0000000..65aa152 --- /dev/null +++ b/lib/features/symptoms_checker/data/symptoms_mapping_data.dart @@ -0,0 +1,323 @@ +import 'package:hmg_patient_app_new/features/symptoms_checker/models/symptom_model.dart'; + +class SymptomsMappingData { + // Map of organ IDs to their possible symptoms + static final Map> organSymptomsMap = { + // HEAD & FACE + 'head': [ + SymptomModel(id: 'head_s1', name: 'Headache', organId: 'head'), + SymptomModel(id: 'head_s2', name: 'Dizziness', organId: 'head'), + SymptomModel(id: 'head_s3', name: 'Migraine', organId: 'head'), + SymptomModel(id: 'head_s4', name: 'Head pressure', organId: 'head'), + SymptomModel(id: 'head_s5', name: 'Vertigo', organId: 'head'), + SymptomModel(id: 'head_s6', name: 'Confusion', organId: 'head'), + SymptomModel(id: 'head_s7', name: 'Memory problems', organId: 'head'), + ], + 'left_eye': [ + SymptomModel(id: 'left_eye_s1', name: 'Blurred vision', organId: 'left_eye'), + SymptomModel(id: 'left_eye_s2', name: 'Eye pain', organId: 'left_eye'), + SymptomModel(id: 'left_eye_s3', name: 'Redness', organId: 'left_eye'), + SymptomModel(id: 'left_eye_s4', name: 'Itching', organId: 'left_eye'), + SymptomModel(id: 'left_eye_s5', name: 'Watering', organId: 'left_eye'), + SymptomModel(id: 'left_eye_s6', name: 'Sensitivity to light', organId: 'left_eye'), + SymptomModel(id: 'left_eye_s7', name: 'Double vision', organId: 'left_eye'), + ], + 'right_eye': [ + SymptomModel(id: 'right_eye_s1', name: 'Blurred vision', organId: 'right_eye'), + SymptomModel(id: 'right_eye_s2', name: 'Eye pain', organId: 'right_eye'), + SymptomModel(id: 'right_eye_s3', name: 'Redness', organId: 'right_eye'), + SymptomModel(id: 'right_eye_s4', name: 'Itching', organId: 'right_eye'), + SymptomModel(id: 'right_eye_s5', name: 'Watering', organId: 'right_eye'), + SymptomModel(id: 'right_eye_s6', name: 'Sensitivity to light', organId: 'right_eye'), + SymptomModel(id: 'right_eye_s7', name: 'Double vision', organId: 'right_eye'), + ], + 'nose_mouth': [ + SymptomModel(id: 'nose_mouth_s1', name: 'Nasal congestion', organId: 'nose_mouth'), + SymptomModel(id: 'nose_mouth_s2', name: 'Runny nose', organId: 'nose_mouth'), + SymptomModel(id: 'nose_mouth_s3', name: 'Nosebleed', organId: 'nose_mouth'), + SymptomModel(id: 'nose_mouth_s4', name: 'Loss of smell', organId: 'nose_mouth'), + SymptomModel(id: 'nose_mouth_s5', name: 'Mouth pain', organId: 'nose_mouth'), + SymptomModel(id: 'nose_mouth_s6', name: 'Dry mouth', organId: 'nose_mouth'), + SymptomModel(id: 'nose_mouth_s7', name: 'Difficulty swallowing', organId: 'nose_mouth'), + ], + + // NECK & THROAT + 'throat': [ + SymptomModel(id: 'throat_s1', name: 'Sore throat', organId: 'throat'), + SymptomModel(id: 'throat_s2', name: 'Difficulty swallowing', organId: 'throat'), + SymptomModel(id: 'throat_s3', name: 'Hoarseness', organId: 'throat'), + SymptomModel(id: 'throat_s4', name: 'Throat pain', organId: 'throat'), + SymptomModel(id: 'throat_s5', name: 'Swollen glands', organId: 'throat'), + SymptomModel(id: 'throat_s6', name: 'Cough', organId: 'throat'), + SymptomModel(id: 'throat_s7', name: 'Difficulty breathing', organId: 'throat'), + ], + + // SHOULDERS + 'left_shoulder': [ + SymptomModel(id: 'left_shoulder_s1', name: 'Shoulder pain', organId: 'left_shoulder'), + SymptomModel(id: 'left_shoulder_s2', name: 'Stiffness', organId: 'left_shoulder'), + SymptomModel(id: 'left_shoulder_s3', name: 'Limited mobility', organId: 'left_shoulder'), + SymptomModel(id: 'left_shoulder_s4', name: 'Swelling', organId: 'left_shoulder'), + SymptomModel(id: 'left_shoulder_s5', name: 'Weakness', organId: 'left_shoulder'), + SymptomModel(id: 'left_shoulder_s6', name: 'Clicking sound', organId: 'left_shoulder'), + ], + 'right_shoulder': [ + SymptomModel(id: 'right_shoulder_s1', name: 'Shoulder pain', organId: 'right_shoulder'), + SymptomModel(id: 'right_shoulder_s2', name: 'Stiffness', organId: 'right_shoulder'), + SymptomModel(id: 'right_shoulder_s3', name: 'Limited mobility', organId: 'right_shoulder'), + SymptomModel(id: 'right_shoulder_s4', name: 'Swelling', organId: 'right_shoulder'), + SymptomModel(id: 'right_shoulder_s5', name: 'Weakness', organId: 'right_shoulder'), + SymptomModel(id: 'right_shoulder_s6', name: 'Clicking sound', organId: 'right_shoulder'), + ], + + // CHEST + 'left_chest': [ + SymptomModel(id: 'left_chest_s1', name: 'Chest pain', organId: 'left_chest'), + SymptomModel(id: 'left_chest_s2', name: 'Tightness', organId: 'left_chest'), + SymptomModel(id: 'left_chest_s3', name: 'Pressure', organId: 'left_chest'), + SymptomModel(id: 'left_chest_s4', name: 'Sharp pain', organId: 'left_chest'), + SymptomModel(id: 'left_chest_s5', name: 'Burning sensation', organId: 'left_chest'), + SymptomModel(id: 'left_chest_s6', name: 'Palpitations', organId: 'left_chest'), + SymptomModel(id: 'left_chest_s7', name: 'Shortness of breath', organId: 'left_chest'), + ], + 'center_chest': [ + SymptomModel(id: 'center_chest_s1', name: 'Chest pain', organId: 'center_chest'), + SymptomModel(id: 'center_chest_s2', name: 'Tightness', organId: 'center_chest'), + SymptomModel(id: 'center_chest_s3', name: 'Pressure', organId: 'center_chest'), + SymptomModel(id: 'center_chest_s4', name: 'Sharp pain', organId: 'center_chest'), + SymptomModel(id: 'center_chest_s5', name: 'Burning sensation', organId: 'center_chest'), + SymptomModel(id: 'center_chest_s6', name: 'Heartburn', organId: 'center_chest'), + SymptomModel(id: 'center_chest_s7', name: 'Difficulty breathing', organId: 'center_chest'), + SymptomModel(id: 'center_chest_s8', name: 'Palpitations', organId: 'center_chest'), + ], + 'right_chest': [ + SymptomModel(id: 'right_chest_s1', name: 'Chest pain', organId: 'right_chest'), + SymptomModel(id: 'right_chest_s2', name: 'Tightness', organId: 'right_chest'), + SymptomModel(id: 'right_chest_s3', name: 'Pressure', organId: 'right_chest'), + SymptomModel(id: 'right_chest_s4', name: 'Sharp pain', organId: 'right_chest'), + SymptomModel(id: 'right_chest_s5', name: 'Burning sensation', organId: 'right_chest'), + SymptomModel(id: 'right_chest_s6', name: 'Shortness of breath', organId: 'right_chest'), + ], + + // RIBS + 'left_ribs': [ + SymptomModel(id: 'left_ribs_s1', name: 'Rib pain', organId: 'left_ribs'), + SymptomModel(id: 'left_ribs_s2', name: 'Pain when breathing', organId: 'left_ribs'), + SymptomModel(id: 'left_ribs_s3', name: 'Tenderness', organId: 'left_ribs'), + SymptomModel(id: 'left_ribs_s4', name: 'Bruising', organId: 'left_ribs'), + SymptomModel(id: 'left_ribs_s5', name: 'Difficulty moving', organId: 'left_ribs'), + ], + 'right_ribs': [ + SymptomModel(id: 'right_ribs_s1', name: 'Rib pain', organId: 'right_ribs'), + SymptomModel(id: 'right_ribs_s2', name: 'Pain when breathing', organId: 'right_ribs'), + SymptomModel(id: 'right_ribs_s3', name: 'Tenderness', organId: 'right_ribs'), + SymptomModel(id: 'right_ribs_s4', name: 'Bruising', organId: 'right_ribs'), + SymptomModel(id: 'right_ribs_s5', name: 'Difficulty moving', organId: 'right_ribs'), + ], + + // ABDOMEN + 'upper_abdomen': [ + SymptomModel(id: 'upper_abdomen_s1', name: 'Abdominal pain', organId: 'upper_abdomen'), + SymptomModel(id: 'upper_abdomen_s2', name: 'Bloating', organId: 'upper_abdomen'), + SymptomModel(id: 'upper_abdomen_s3', name: 'Nausea', organId: 'upper_abdomen'), + SymptomModel(id: 'upper_abdomen_s4', name: 'Vomiting', organId: 'upper_abdomen'), + SymptomModel(id: 'upper_abdomen_s5', name: 'Heartburn', organId: 'upper_abdomen'), + SymptomModel(id: 'upper_abdomen_s6', name: 'Loss of appetite', organId: 'upper_abdomen'), + SymptomModel(id: 'upper_abdomen_s7', name: 'Indigestion', organId: 'upper_abdomen'), + ], + 'navel': [ + SymptomModel(id: 'navel_s1', name: 'Abdominal pain', organId: 'navel'), + SymptomModel(id: 'navel_s2', name: 'Cramping', organId: 'navel'), + SymptomModel(id: 'navel_s3', name: 'Bloating', organId: 'navel'), + SymptomModel(id: 'navel_s4', name: 'Gas', organId: 'navel'), + SymptomModel(id: 'navel_s5', name: 'Tenderness', organId: 'navel'), + ], + 'lower_abdomen': [ + SymptomModel(id: 'lower_abdomen_s1', name: 'Lower abdominal pain', organId: 'lower_abdomen'), + SymptomModel(id: 'lower_abdomen_s2', name: 'Cramping', organId: 'lower_abdomen'), + SymptomModel(id: 'lower_abdomen_s3', name: 'Bloating', organId: 'lower_abdomen'), + SymptomModel(id: 'lower_abdomen_s4', name: 'Constipation', organId: 'lower_abdomen'), + SymptomModel(id: 'lower_abdomen_s5', name: 'Diarrhea', organId: 'lower_abdomen'), + SymptomModel(id: 'lower_abdomen_s6', name: 'Urinary discomfort', organId: 'lower_abdomen'), + ], + + // PELVIS & GROIN + 'left_groin': [ + SymptomModel(id: 'left_groin_s1', name: 'Groin pain', organId: 'left_groin'), + SymptomModel(id: 'left_groin_s2', name: 'Swelling', organId: 'left_groin'), + SymptomModel(id: 'left_groin_s3', name: 'Tenderness', organId: 'left_groin'), + SymptomModel(id: 'left_groin_s4', name: 'Limited mobility', organId: 'left_groin'), + ], + 'right_groin': [ + SymptomModel(id: 'right_groin_s1', name: 'Groin pain', organId: 'right_groin'), + SymptomModel(id: 'right_groin_s2', name: 'Swelling', organId: 'right_groin'), + SymptomModel(id: 'right_groin_s3', name: 'Tenderness', organId: 'right_groin'), + SymptomModel(id: 'right_groin_s4', name: 'Limited mobility', organId: 'right_groin'), + ], + + // ARMS - LEFT + 'left_elbow': [ + SymptomModel(id: 'left_elbow_s1', name: 'Elbow pain', organId: 'left_elbow'), + SymptomModel(id: 'left_elbow_s2', name: 'Stiffness', organId: 'left_elbow'), + SymptomModel(id: 'left_elbow_s3', name: 'Swelling', organId: 'left_elbow'), + SymptomModel(id: 'left_elbow_s4', name: 'Limited mobility', organId: 'left_elbow'), + SymptomModel(id: 'left_elbow_s5', name: 'Tingling', organId: 'left_elbow'), + ], + 'left_forearm': [ + SymptomModel(id: 'left_forearm_s1', name: 'Forearm pain', organId: 'left_forearm'), + SymptomModel(id: 'left_forearm_s2', name: 'Weakness', organId: 'left_forearm'), + SymptomModel(id: 'left_forearm_s3', name: 'Numbness', organId: 'left_forearm'), + SymptomModel(id: 'left_forearm_s4', name: 'Tingling', organId: 'left_forearm'), + SymptomModel(id: 'left_forearm_s5', name: 'Swelling', organId: 'left_forearm'), + ], + 'left_wrist': [ + SymptomModel(id: 'left_wrist_s1', name: 'Wrist pain', organId: 'left_wrist'), + SymptomModel(id: 'left_wrist_s2', name: 'Stiffness', organId: 'left_wrist'), + SymptomModel(id: 'left_wrist_s3', name: 'Swelling', organId: 'left_wrist'), + SymptomModel(id: 'left_wrist_s4', name: 'Limited mobility', organId: 'left_wrist'), + SymptomModel(id: 'left_wrist_s5', name: 'Tingling', organId: 'left_wrist'), + SymptomModel(id: 'left_wrist_s6', name: 'Numbness', organId: 'left_wrist'), + ], + + // ARMS - RIGHT + 'right_elbow': [ + SymptomModel(id: 'right_elbow_s1', name: 'Elbow pain', organId: 'right_elbow'), + SymptomModel(id: 'right_elbow_s2', name: 'Stiffness', organId: 'right_elbow'), + SymptomModel(id: 'right_elbow_s3', name: 'Swelling', organId: 'right_elbow'), + SymptomModel(id: 'right_elbow_s4', name: 'Limited mobility', organId: 'right_elbow'), + SymptomModel(id: 'right_elbow_s5', name: 'Tingling', organId: 'right_elbow'), + ], + 'right_forearm': [ + SymptomModel(id: 'right_forearm_s1', name: 'Forearm pain', organId: 'right_forearm'), + SymptomModel(id: 'right_forearm_s2', name: 'Weakness', organId: 'right_forearm'), + SymptomModel(id: 'right_forearm_s3', name: 'Numbness', organId: 'right_forearm'), + SymptomModel(id: 'right_forearm_s4', name: 'Tingling', organId: 'right_forearm'), + SymptomModel(id: 'right_forearm_s5', name: 'Swelling', organId: 'right_forearm'), + ], + 'right_wrist': [ + SymptomModel(id: 'right_wrist_s1', name: 'Wrist pain', organId: 'right_wrist'), + SymptomModel(id: 'right_wrist_s2', name: 'Stiffness', organId: 'right_wrist'), + SymptomModel(id: 'right_wrist_s3', name: 'Swelling', organId: 'right_wrist'), + SymptomModel(id: 'right_wrist_s4', name: 'Limited mobility', organId: 'right_wrist'), + SymptomModel(id: 'right_wrist_s5', name: 'Tingling', organId: 'right_wrist'), + SymptomModel(id: 'right_wrist_s6', name: 'Numbness', organId: 'right_wrist'), + ], + + // LEGS - LEFT + 'left_thigh': [ + SymptomModel(id: 'left_thigh_s1', name: 'Thigh pain', organId: 'left_thigh'), + SymptomModel(id: 'left_thigh_s2', name: 'Muscle cramps', organId: 'left_thigh'), + SymptomModel(id: 'left_thigh_s3', name: 'Weakness', organId: 'left_thigh'), + SymptomModel(id: 'left_thigh_s4', name: 'Numbness', organId: 'left_thigh'), + SymptomModel(id: 'left_thigh_s5', name: 'Swelling', organId: 'left_thigh'), + ], + 'left_knee': [ + SymptomModel(id: 'left_knee_s1', name: 'Knee pain', organId: 'left_knee'), + SymptomModel(id: 'left_knee_s2', name: 'Stiffness', organId: 'left_knee'), + SymptomModel(id: 'left_knee_s3', name: 'Swelling', organId: 'left_knee'), + SymptomModel(id: 'left_knee_s4', name: 'Limited mobility', organId: 'left_knee'), + SymptomModel(id: 'left_knee_s5', name: 'Clicking sound', organId: 'left_knee'), + SymptomModel(id: 'left_knee_s6', name: 'Instability', organId: 'left_knee'), + ], + 'left_shin': [ + SymptomModel(id: 'left_shin_s1', name: 'Shin pain', organId: 'left_shin'), + SymptomModel(id: 'left_shin_s2', name: 'Tenderness', organId: 'left_shin'), + SymptomModel(id: 'left_shin_s3', name: 'Swelling', organId: 'left_shin'), + SymptomModel(id: 'left_shin_s4', name: 'Numbness', organId: 'left_shin'), + ], + 'left_ankle': [ + SymptomModel(id: 'left_ankle_s1', name: 'Ankle pain', organId: 'left_ankle'), + SymptomModel(id: 'left_ankle_s2', name: 'Stiffness', organId: 'left_ankle'), + SymptomModel(id: 'left_ankle_s3', name: 'Swelling', organId: 'left_ankle'), + SymptomModel(id: 'left_ankle_s4', name: 'Limited mobility', organId: 'left_ankle'), + SymptomModel(id: 'left_ankle_s5', name: 'Instability', organId: 'left_ankle'), + ], + + // LEGS - RIGHT + 'right_thigh': [ + SymptomModel(id: 'right_thigh_s1', name: 'Thigh pain', organId: 'right_thigh'), + SymptomModel(id: 'right_thigh_s2', name: 'Muscle cramps', organId: 'right_thigh'), + SymptomModel(id: 'right_thigh_s3', name: 'Weakness', organId: 'right_thigh'), + SymptomModel(id: 'right_thigh_s4', name: 'Numbness', organId: 'right_thigh'), + SymptomModel(id: 'right_thigh_s5', name: 'Swelling', organId: 'right_thigh'), + ], + 'right_knee': [ + SymptomModel(id: 'right_knee_s1', name: 'Knee pain', organId: 'right_knee'), + SymptomModel(id: 'right_knee_s2', name: 'Stiffness', organId: 'right_knee'), + SymptomModel(id: 'right_knee_s3', name: 'Swelling', organId: 'right_knee'), + SymptomModel(id: 'right_knee_s4', name: 'Limited mobility', organId: 'right_knee'), + SymptomModel(id: 'right_knee_s5', name: 'Clicking sound', organId: 'right_knee'), + SymptomModel(id: 'right_knee_s6', name: 'Instability', organId: 'right_knee'), + ], + 'right_shin': [ + SymptomModel(id: 'right_shin_s1', name: 'Shin pain', organId: 'right_shin'), + SymptomModel(id: 'right_shin_s2', name: 'Tenderness', organId: 'right_shin'), + SymptomModel(id: 'right_shin_s3', name: 'Swelling', organId: 'right_shin'), + SymptomModel(id: 'right_shin_s4', name: 'Numbness', organId: 'right_shin'), + ], + 'right_ankle': [ + SymptomModel(id: 'right_ankle_s1', name: 'Ankle pain', organId: 'right_ankle'), + SymptomModel(id: 'right_ankle_s2', name: 'Stiffness', organId: 'right_ankle'), + SymptomModel(id: 'right_ankle_s3', name: 'Swelling', organId: 'right_ankle'), + SymptomModel(id: 'right_ankle_s4', name: 'Limited mobility', organId: 'right_ankle'), + SymptomModel(id: 'right_ankle_s5', name: 'Instability', organId: 'right_ankle'), + ], + + // BACK VIEW ORGANS + 'back_head': [ + SymptomModel(id: 'back_head_s1', name: 'Back of head pain', organId: 'back_head'), + SymptomModel(id: 'back_head_s2', name: 'Neck stiffness', organId: 'back_head'), + SymptomModel(id: 'back_head_s3', name: 'Tension headache', organId: 'back_head'), + ], + 'neck': [ + SymptomModel(id: 'neck_s1', name: 'Neck pain', organId: 'neck'), + SymptomModel(id: 'neck_s2', name: 'Stiffness', organId: 'neck'), + SymptomModel(id: 'neck_s3', name: 'Limited mobility', organId: 'neck'), + SymptomModel(id: 'neck_s4', name: 'Muscle spasm', organId: 'neck'), + SymptomModel(id: 'neck_s5', name: 'Tingling', organId: 'neck'), + ], + 'upper_back': [ + SymptomModel(id: 'upper_back_s1', name: 'Upper back pain', organId: 'upper_back'), + SymptomModel(id: 'upper_back_s2', name: 'Stiffness', organId: 'upper_back'), + SymptomModel(id: 'upper_back_s3', name: 'Muscle tension', organId: 'upper_back'), + SymptomModel(id: 'upper_back_s4', name: 'Sharp pain', organId: 'upper_back'), + SymptomModel(id: 'upper_back_s5', name: 'Difficulty breathing', organId: 'upper_back'), + ], + 'mid_back': [ + SymptomModel(id: 'mid_back_s1', name: 'Mid back pain', organId: 'mid_back'), + SymptomModel(id: 'mid_back_s2', name: 'Stiffness', organId: 'mid_back'), + SymptomModel(id: 'mid_back_s3', name: 'Muscle spasm', organId: 'mid_back'), + SymptomModel(id: 'mid_back_s4', name: 'Tenderness', organId: 'mid_back'), + ], + 'lower_back': [ + SymptomModel(id: 'lower_back_s1', name: 'Lower back pain', organId: 'lower_back'), + SymptomModel(id: 'lower_back_s2', name: 'Stiffness', organId: 'lower_back'), + SymptomModel(id: 'lower_back_s3', name: 'Sharp pain', organId: 'lower_back'), + SymptomModel(id: 'lower_back_s4', name: 'Sciatica', organId: 'lower_back'), + SymptomModel(id: 'lower_back_s5', name: 'Limited mobility', organId: 'lower_back'), + SymptomModel(id: 'lower_back_s6', name: 'Muscle spasm', organId: 'lower_back'), + ], + }; + + /// Get symptoms for a specific organ ID + static List getSymptomsForOrgan(String organId) { + return organSymptomsMap[organId] ?? []; + } + + /// Get symptoms for multiple organ IDs + static Map> getSymptomsForOrgans(List organIds) { + Map> result = {}; + for (String organId in organIds) { + List symptoms = getSymptomsForOrgan(organId); + if (symptoms.isNotEmpty) { + result[organId] = symptoms; + } + } + return result; + } + + /// Check if an organ has symptoms mapped + static bool hasSymptoms(String organId) { + return organSymptomsMap.containsKey(organId) && organSymptomsMap[organId]!.isNotEmpty; + } +} diff --git a/lib/features/symptoms_checker/models/symptom_model.dart b/lib/features/symptoms_checker/models/symptom_model.dart new file mode 100644 index 0000000..d35b11a --- /dev/null +++ b/lib/features/symptoms_checker/models/symptom_model.dart @@ -0,0 +1,55 @@ +class SymptomModel { + final String id; + final String name; + final String organId; + + const SymptomModel({ + required this.id, + required this.name, + required this.organId, + }); + + SymptomModel copyWith({ + String? id, + String? name, + String? organId, + }) { + return SymptomModel( + id: id ?? this.id, + name: name ?? this.name, + organId: organId ?? this.organId, + ); + } +} + +class OrganSymptomsGroup { + final String organId; + final String organName; + final List symptoms; + final Set selectedSymptomIds; + + OrganSymptomsGroup({ + required this.organId, + required this.organName, + required this.symptoms, + Set? selectedSymptomIds, + }) : selectedSymptomIds = selectedSymptomIds ?? {}; + + bool get hasSelectedSymptoms => selectedSymptomIds.isNotEmpty; + + int get selectedCount => selectedSymptomIds.length; + + OrganSymptomsGroup copyWith({ + String? organId, + String? organName, + List? symptoms, + Set? selectedSymptomIds, + }) { + return OrganSymptomsGroup( + organId: organId ?? this.organId, + organName: organName ?? this.organName, + symptoms: symptoms ?? this.symptoms, + selectedSymptomIds: selectedSymptomIds ?? this.selectedSymptomIds, + ); + } +} diff --git a/lib/presentation/symptoms_checker/organ_selector_screen.dart b/lib/presentation/symptoms_checker/organ_selector_screen.dart index 738adff..e47032c 100644 --- a/lib/presentation/symptoms_checker/organ_selector_screen.dart +++ b/lib/presentation/symptoms_checker/organ_selector_screen.dart @@ -42,13 +42,7 @@ class _OrganSelectorPageState extends State { return; } - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Selected organs:\n\n ${viewModel.getSelectedOrganIds()}'), - backgroundColor: AppColors.successColor, - ), - ); - context.navigateWithName(AppRoutes.triageProgressScreen); + context.navigateWithName(AppRoutes.symptomsCheckerScreen); } @override diff --git a/lib/presentation/symptoms_checker/symptoms_checker_view_model.dart b/lib/presentation/symptoms_checker/symptoms_checker_view_model.dart index e162b67..9d42dc7 100644 --- a/lib/presentation/symptoms_checker/symptoms_checker_view_model.dart +++ b/lib/presentation/symptoms_checker/symptoms_checker_view_model.dart @@ -2,7 +2,9 @@ import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/data/symptoms_mapping_data.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/organ_model.dart'; +import 'package:hmg_patient_app_new/features/symptoms_checker/models/symptom_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/organ_mapping_data.dart'; class SymptomsCheckerViewModel extends ChangeNotifier { @@ -12,6 +14,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier { final Set _selectedOrganIds = {}; bool _isBottomSheetExpanded = false; + // Symptom selection state + final Map _organSymptomsGroups = {}; + // Tooltip state String? _tooltipOrganId; Timer? _tooltipTimer; @@ -46,6 +51,21 @@ class SymptomsCheckerViewModel extends ChangeNotifier { /// Get count of selected organs int get selectedOrgansCount => _selectedOrganIds.length; + /// Get organ symptoms groups for selected organs + List get organSymptomsGroups { + return _organSymptomsGroups.values.toList(); + } + + /// Get total selected symptoms count across all organs + int get totalSelectedSymptomsCount { + return _organSymptomsGroups.values.fold(0, (sum, group) => sum + group.selectedCount); + } + + /// Check if at least one symptom is selected + bool get hasSelectedSymptoms { + return _organSymptomsGroups.values.any((group) => group.hasSelectedSymptoms); + } + // Methods /// Toggle between front and back body view @@ -135,10 +155,76 @@ class SymptomsCheckerViewModel extends ChangeNotifier { return selectedOrgans.map((organ) => organ.name).toList(); } + /// Initialize symptom groups based on selected organs + void initializeSymptomGroups() { + _organSymptomsGroups.clear(); + + for (String organId in _selectedOrganIds) { + List symptoms = SymptomsMappingData.getSymptomsForOrgan(organId); + if (symptoms.isNotEmpty) { + // Find organ name from selectedOrgans + String organName = selectedOrgans + .firstWhere((organ) => organ.id == organId, orElse: () => OrganModel(id: organId, name: organId, bodyView: BodyView.front, position: OrganPosition(x: 0, y: 0))) + .name; + + _organSymptomsGroups[organId] = OrganSymptomsGroup( + organId: organId, + organName: organName, + symptoms: symptoms, + ); + } + } + notifyListeners(); + } + + /// Toggle symptom selection for a specific organ + void toggleSymptomSelection(String organId, String symptomId) { + if (_organSymptomsGroups.containsKey(organId)) { + final group = _organSymptomsGroups[organId]!; + final selectedIds = Set.from(group.selectedSymptomIds); + + if (selectedIds.contains(symptomId)) { + selectedIds.remove(symptomId); + } else { + selectedIds.add(symptomId); + } + + _organSymptomsGroups[organId] = group.copyWith(selectedSymptomIds: selectedIds); + notifyListeners(); + } + } + + /// Check if a symptom is selected + bool isSymptomSelected(String organId, String symptomId) { + return _organSymptomsGroups[organId]?.selectedSymptomIds.contains(symptomId) ?? false; + } + + /// Get all selected symptoms across all organs + List getAllSelectedSymptoms() { + List allSymptoms = []; + for (var group in _organSymptomsGroups.values) { + for (var symptom in group.symptoms) { + if (group.selectedSymptomIds.contains(symptom.id)) { + allSymptoms.add(symptom); + } + } + } + return allSymptoms; + } + + /// Clear all symptom selections + void clearAllSymptomSelections() { + for (var organId in _organSymptomsGroups.keys) { + _organSymptomsGroups[organId] = _organSymptomsGroups[organId]!.copyWith(selectedSymptomIds: {}); + } + notifyListeners(); + } + /// Reset the view model to initial state void reset() { _currentView = BodyView.front; _selectedOrganIds.clear(); + _organSymptomsGroups.clear(); _isBottomSheetExpanded = false; _tooltipTimer?.cancel(); _tooltipOrganId = null; diff --git a/lib/presentation/symptoms_checker/symptoms_selector_screen.dart b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart new file mode 100644 index 0000000..b1c3234 --- /dev/null +++ b/lib/presentation/symptoms_checker/symptoms_selector_screen.dart @@ -0,0 +1,224 @@ +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/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/models/symptom_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/symptoms_checker/symptoms_checker_view_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/custom_selectable_chip.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:provider/provider.dart'; + +class SymptomsSelectorScreen extends StatefulWidget { + const SymptomsSelectorScreen({super.key}); + + @override + State createState() => _SymptomsSelectorScreenState(); +} + +class _SymptomsSelectorScreenState extends State { + @override + void initState() { + super.initState(); + // Initialize symptom groups based on selected organs + WidgetsBinding.instance.addPostFrameCallback((_) { + final viewModel = context.read(); + viewModel.initializeSymptomGroups(); + }); + } + + void _onNextPressed(SymptomsCheckerViewModel viewModel) { + if (viewModel.hasSelectedSymptoms) { + // Navigate to triage screen + context.navigateWithName(AppRoutes.triageScreen); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Please select at least one symptom before proceeding'.needTranslation), + backgroundColor: AppColors.errorColor, + ), + ); + } + } + + void _onPreviousPressed() { + context.pop(); + } + + _buildConfirmationBottomSheet({required BuildContext context, required VoidCallback onConfirm}) { + return showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: "Are you sure you want to restart the organ selection?".needTranslation, + isShowActionButtons: true, + onCancelTap: () => Navigator.pop(context), + onConfirmTap: () => onConfirm(), + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Consumer( + builder: (context, viewModel, _) { + return Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Symptoms Selector".needTranslation, + onLeadingTapped: () => _buildConfirmationBottomSheet( + context: context, + onConfirm: () => { + context.pop(), + context.pop(), + }), + child: viewModel.organSymptomsGroups.isEmpty + ? _buildEmptyState() + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + ...viewModel.organSymptomsGroups.map((group) { + return Padding( + padding: EdgeInsets.only(bottom: 16.h), + child: _buildSymptomsSelectionCard(viewModel, group), + ); + }), + ], + ), + ), + ), + _buildStickyBottomCard(context, viewModel), + ], + ); + }, + ), + ); + } + + Widget _buildSymptomsSelectionCard(SymptomsCheckerViewModel viewModel, OrganSymptomsGroup group) { + return Container( + width: double.infinity, + margin: EdgeInsets.symmetric(horizontal: 24.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), + padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + 'Possible symptoms related to "${group.organName}"', + style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, color: AppColors.textColor), + ), + ), + ], + ), + SizedBox(height: 24.h), + Wrap( + runSpacing: 12.h, + spacing: 8.w, + children: group.symptoms.map((symptom) { + bool isSelected = viewModel.isSymptomSelected(group.organId, symptom.id); + return GestureDetector( + onTap: () => viewModel.toggleSymptomSelection(group.organId, symptom.id), + child: CustomSelectableChip( + label: symptom.name, + selected: isSelected, + activeColor: AppColors.primaryRedBorderColor, + activeTextColor: AppColors.primaryRedBorderColor, + inactiveBorderColor: AppColors.bottomNAVBorder, + inactiveTextColor: AppColors.textColor, + ), + ); + }).toList(), + ), + ], + ), + ); + } + + Widget _buildEmptyState() { + return Center( + child: Padding( + padding: EdgeInsets.all(24.h), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.info_outline, size: 64.h, color: AppColors.greyTextColor), + SizedBox(height: 16.h), + Text( + 'No organs selected'.needTranslation, + style: TextStyle( + fontSize: 18.f, + fontWeight: FontWeight.w600, + color: AppColors.textColor, + ), + ), + SizedBox(height: 8.h), + Text( + 'Please go back and select organs first'.needTranslation, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14.f, + color: AppColors.greyTextColor, + ), + ), + ], + ), + ), + ); + } + + Widget _buildStickyBottomCard(BuildContext context, SymptomsCheckerViewModel viewModel) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: "Previous".needTranslation, + onPressed: _onPreviousPressed, + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11), + borderColor: Colors.transparent, + textColor: AppColors.primaryRedColor, + fontSize: 16.f, + ), + ), + SizedBox(width: 12.w), + Expanded( + child: CustomButton( + text: "Next".needTranslation, + onPressed: () => _onNextPressed(viewModel), + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16.f, + ), + ), + ], + ), + SizedBox(height: 24.h), + ], + ).paddingSymmetrical(24.w, 0), + ); + } +} diff --git a/lib/presentation/symptoms_checker/triage_progress_screen.dart b/lib/presentation/symptoms_checker/triage_screen.dart similarity index 94% rename from lib/presentation/symptoms_checker/triage_progress_screen.dart rename to lib/presentation/symptoms_checker/triage_screen.dart index a3d0c1a..4dbe0ca 100644 --- a/lib/presentation/symptoms_checker/triage_progress_screen.dart +++ b/lib/presentation/symptoms_checker/triage_screen.dart @@ -14,14 +14,14 @@ import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; -class TriageProgressScreen extends StatefulWidget { - const TriageProgressScreen({super.key}); +class TriageScreen extends StatefulWidget { + const TriageScreen({super.key}); @override - State createState() => _TriageProgressScreenState(); + State createState() => _TriageScreenState(); } -class _TriageProgressScreenState extends State { +class _TriageScreenState extends State { late List triageQuestions; int currentQuestionIndex = 0; @@ -96,12 +96,14 @@ class _TriageProgressScreenState extends State { Expanded( child: CollapsingListView( title: "Triage".needTranslation, - onLeadingTapped: () => _buildConfirmationBottomSheet( - context: context, - onConfirm: () => { - context.pop(), - context.pop(), - }), + // onLeadingTapped: () => _buildConfirmationBottomSheet( + // context: context, + // onConfirm: () => { + // context.pop(), + // context.pop(), + // }), + + onLeadingTapped: () => context.pop(), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -176,8 +178,8 @@ class _TriageProgressScreenState extends State { height: 24.w, decoration: BoxDecoration( color: selected ? AppColors.primaryRedColor : Colors.transparent, - borderRadius: BorderRadius.circular(8.r), - border: Border.all(color: selected ? AppColors.primaryRedColor : AppColors.borderGrayColor, width: 2.w), + borderRadius: BorderRadius.circular(5.r), + border: Border.all(color: selected ? AppColors.primaryRedColor : AppColors.borderGrayColor, width: 1.w), ), child: selected ? Icon(Icons.check, size: 16.f, color: AppColors.whiteColor) : null, ), diff --git a/lib/presentation/symptoms_checker/widgets/interactive_body_widget.dart b/lib/presentation/symptoms_checker/widgets/interactive_body_widget.dart index 4c41ddd..34cc049 100644 --- a/lib/presentation/symptoms_checker/widgets/interactive_body_widget.dart +++ b/lib/presentation/symptoms_checker/widgets/interactive_body_widget.dart @@ -114,15 +114,13 @@ class _InteractiveBodyWidgetState extends State { // Tooltip if (widget.tooltipOrganId != null) - ...widget.organs.where((organ) => organ.id == widget.tooltipOrganId).map((organ) { - final centerX = organ.position.x * imageConstraints.maxWidth; - final centerY = organ.position.y * imageConstraints.maxHeight; - - return OrganTooltipWidget( - organName: organ.name, - position: Offset(centerX, centerY), - ); - }), + ...widget.organs.where((organ) => organ.id == widget.tooltipOrganId).map( + (organ) { + final centerX = organ.position.x * imageConstraints.maxWidth; + final centerY = organ.position.y * imageConstraints.maxHeight; + return OrganTooltipWidget(organName: organ.name, position: Offset(centerX, centerY)); + }, + ), ], ); }, diff --git a/lib/presentation/symptoms_checker/widgets/organ_tooltip_widget.dart b/lib/presentation/symptoms_checker/widgets/organ_tooltip_widget.dart index 77e8319..6c2625b 100644 --- a/lib/presentation/symptoms_checker/widgets/organ_tooltip_widget.dart +++ b/lib/presentation/symptoms_checker/widgets/organ_tooltip_widget.dart @@ -18,11 +18,15 @@ class OrganTooltipWidget extends StatelessWidget { translation: const Offset(-0.5, -1.2), // Center horizontally, position above the dot child: Container( padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.scaffoldBgColor, borderRadius: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.scaffoldBgColor, + borderRadius: 8.h, + hasShadow: true, + ), child: Text( organName, style: TextStyle( - color: AppColors.primaryRedColor, + color: AppColors.textColor, fontSize: 12.f, fontWeight: FontWeight.w500, ), diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 0e6ec4d..36869f6 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -9,7 +9,8 @@ import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/organ_selector_screen.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/possible_conditions_screen.dart'; -import 'package:hmg_patient_app_new/presentation/symptoms_checker/triage_progress_screen.dart'; +import 'package:hmg_patient_app_new/presentation/symptoms_checker/symptoms_selector_screen.dart'; +import 'package:hmg_patient_app_new/presentation/symptoms_checker/triage_screen.dart'; import 'package:hmg_patient_app_new/splashPage.dart'; class AppRoutes { @@ -25,8 +26,9 @@ class AppRoutes { // Symptoms Checker static const String organSelectorPage = '/organSelectorPage'; + static const String symptomsCheckerScreen = '/symptomsCheckerScreen'; static const String possibleConditionsScreen = '/possibleConditionsScreen'; - static const String triageProgressScreen = '/triageProgressScreen'; + static const String triageScreen = '/triageProgressScreen'; static Map get routes => { initialRoute: (context) => SplashPage(), @@ -39,7 +41,8 @@ class AppRoutes { comprehensiveCheckupPage: (context) => ComprehensiveCheckupPage(), homeHealthCarePage: (context) => HhcProceduresPage(), organSelectorPage: (context) => OrganSelectorPage(), + symptomsCheckerScreen: (context) => SymptomsSelectorScreen(), possibleConditionsScreen: (context) => PossibleConditionsScreen(), - triageProgressScreen: (context) => TriageProgressScreen() + triageScreen: (context) => TriageScreen() }; } diff --git a/lib/widgets/chip/custom_chip_widget.dart b/lib/widgets/chip/custom_chip_widget.dart index 5dee4f0..b60db57 100644 --- a/lib/widgets/chip/custom_chip_widget.dart +++ b/lib/widgets/chip/custom_chip_widget.dart @@ -67,7 +67,12 @@ class CustomChipWidget extends StatelessWidget { Utils.buildSvgWithAssets(icon: iconAsset!), SizedBox(width: 4.h), ], - chipText.toText10(isBold: true, color: isSelected ? Colors.white : textColor ?? chipType.color, maxlines: 1, weight: FontWeight.w500, letterSpacing: -0.5), + chipText.toText10( + isBold: true, + color: isSelected ? Colors.white : textColor ?? chipType.color, + maxlines: 1, + weight: FontWeight.w500, + letterSpacing: -0.5), ], ), ), diff --git a/lib/widgets/chip/custom_selectable_chip.dart b/lib/widgets/chip/custom_selectable_chip.dart new file mode 100644 index 0000000..be42bfd --- /dev/null +++ b/lib/widgets/chip/custom_selectable_chip.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class CustomSelectableChip extends StatelessWidget { + final String label; + final bool selected; + final VoidCallback? onTap; + final Color activeColor; + final Color activeTextColor; + final Color inactiveBorderColor; + final Color inactiveTextColor; + + const CustomSelectableChip({ + super.key, + required this.label, + required this.selected, + this.onTap, + this.activeColor = const Color(0xFFD03C32), // red accent + this.activeTextColor = Colors.white, + this.inactiveBorderColor = const Color(0xFFE8E8E8), + this.inactiveTextColor = const Color(0xFF222222), + }); + + @override + Widget build(BuildContext context) { + final double radius = 8.0; + return AnimatedContainer( + duration: const Duration(milliseconds: 180), + padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 8.h), + decoration: BoxDecoration( + color: selected ? activeColor.withValues(alpha: 0.12) : AppColors.whiteColor, + borderRadius: BorderRadius.circular(radius), + border: Border.all( + color: selected ? activeColor : inactiveBorderColor, + width: 1, + ), + ), + child: InkWell( + borderRadius: BorderRadius.circular(radius), + onTap: onTap, + child: Text( + label, + style: TextStyle( + fontSize: 12.f, + fontWeight: FontWeight.w500, + color: selected ? activeTextColor : inactiveTextColor, + letterSpacing: -0.02 * 18, + height: 1.0, + ), + ), + ), + ); + } +}