Added symptoms selector

pull/113/head
faizatflutter 3 months ago
parent ac84d268e9
commit 2173d091ed

@ -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<String, List<SymptomModel>> 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<SymptomModel> getSymptomsForOrgan(String organId) {
return organSymptomsMap[organId] ?? [];
}
/// Get symptoms for multiple organ IDs
static Map<String, List<SymptomModel>> getSymptomsForOrgans(List<String> organIds) {
Map<String, List<SymptomModel>> result = {};
for (String organId in organIds) {
List<SymptomModel> 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;
}
}

@ -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<SymptomModel> symptoms;
final Set<String> selectedSymptomIds;
OrganSymptomsGroup({
required this.organId,
required this.organName,
required this.symptoms,
Set<String>? selectedSymptomIds,
}) : selectedSymptomIds = selectedSymptomIds ?? {};
bool get hasSelectedSymptoms => selectedSymptomIds.isNotEmpty;
int get selectedCount => selectedSymptomIds.length;
OrganSymptomsGroup copyWith({
String? organId,
String? organName,
List<SymptomModel>? symptoms,
Set<String>? selectedSymptomIds,
}) {
return OrganSymptomsGroup(
organId: organId ?? this.organId,
organName: organName ?? this.organName,
symptoms: symptoms ?? this.symptoms,
selectedSymptomIds: selectedSymptomIds ?? this.selectedSymptomIds,
);
}
}

@ -42,13 +42,7 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
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

@ -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<String> _selectedOrganIds = {};
bool _isBottomSheetExpanded = false;
// Symptom selection state
final Map<String, OrganSymptomsGroup> _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<OrganSymptomsGroup> 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<SymptomModel> 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<String>.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<SymptomModel> getAllSelectedSymptoms() {
List<SymptomModel> 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;

@ -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<SymptomsSelectorScreen> createState() => _SymptomsSelectorScreenState();
}
class _SymptomsSelectorScreenState extends State<SymptomsSelectorScreen> {
@override
void initState() {
super.initState();
// Initialize symptom groups based on selected organs
WidgetsBinding.instance.addPostFrameCallback((_) {
final viewModel = context.read<SymptomsCheckerViewModel>();
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<SymptomsCheckerViewModel>(
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),
);
}
}

@ -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<TriageProgressScreen> createState() => _TriageProgressScreenState();
State<TriageScreen> createState() => _TriageScreenState();
}
class _TriageProgressScreenState extends State<TriageProgressScreen> {
class _TriageScreenState extends State<TriageScreen> {
late List<TriageQuestionModel> triageQuestions;
int currentQuestionIndex = 0;
@ -96,12 +96,14 @@ class _TriageProgressScreenState extends State<TriageProgressScreen> {
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<TriageProgressScreen> {
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,
),

@ -114,15 +114,13 @@ class _InteractiveBodyWidgetState extends State<InteractiveBodyWidget> {
// 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));
},
),
],
);
},

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

@ -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<String, WidgetBuilder> 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()
};
}

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

@ -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,
),
),
),
);
}
}
Loading…
Cancel
Save