Symptoms Checker flow is completed

pull/163/head
faizatflutter 6 days ago
parent 4ceca016bb
commit 07447e148b

@ -0,0 +1,5 @@
<svg width="24" height="22" viewBox="0 0 24 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="7" cy="7" r="7" fill="#0B85F7" fill-opacity="0.75"/>
<circle cx="17" cy="7" r="7" fill="#18C273" fill-opacity="0.75"/>
<circle cx="12" cy="15" r="7" fill="#ED1C2B" fill-opacity="0.75"/>
</svg>

After

Width:  |  Height:  |  Size: 301 B

@ -841,7 +841,7 @@ class ApiConsts {
static final String getRiskFactors = '$symptomsCheckerApi/GetRiskFactors';
static final String getSuggestions = '$symptomsCheckerApi/GetSuggestion';
static final String diagnosis = '$symptomsCheckerApi/GetDiagnosis';
static final String explain = '$symptomsCheckerApi/explain';
static final String explain = '$symptomsCheckerApi/ExplainDiagnosisResult';
//E-REFERRAL SERVICES
static final getAllRelationshipTypes = "Services/Patients.svc/REST/GetAllRelationshipTypes";

@ -253,6 +253,7 @@ class AppAssets {
static const String homeBorderedIcon = '$svgBasePath/home_bordered.svg';
static const String symptomCheckerIcon = '$svgBasePath/symptom_checker_icon.svg';
static const String symptomCheckerBottomIcon = '$svgBasePath/symptom_bottom_icon.svg';
static const String coloredDotsIcon = '$svgBasePath/colored_dots_icon.svg';
// Water Monitor
static const String waterBottle = '$svgBasePath/water_bottle.svg';
@ -352,4 +353,3 @@ class AppAnimations {
static const String ambulanceAlert = '$lottieBasePath/ambulance_alert.json';
static const String rrtAmbulance = '$lottieBasePath/rrt_ambulance.json';
}

@ -311,3 +311,150 @@ extension ServiceTypeEnumExt on ServiceTypeEnum {
enum PossibleConditionsSeverityEnum { seekMedicalAdvice, monitorOnly, emergency }
enum HealthTrackerTypeEnum { bloodSugar, bloodPressure, weightTracker }
// Severity Enum
enum SeverityEnum { mild, moderate, severe }
extension SeverityEnumExtension on SeverityEnum {
int get toInt {
switch (this) {
case SeverityEnum.mild:
return 0;
case SeverityEnum.moderate:
return 1;
case SeverityEnum.severe:
return 2;
}
}
String get displayName {
AppState appState = getIt.get<AppState>();
bool isArabic = appState.getLanguageID() == 1 ? true : false;
switch (this) {
case SeverityEnum.mild:
return isArabic ? 'خفيف' : 'Mild';
case SeverityEnum.moderate:
return isArabic ? 'متوسط' : 'Moderate';
case SeverityEnum.severe:
return isArabic ? 'شديد' : 'Severe';
}
}
static SeverityEnum? fromInt(int value) {
switch (value) {
case 0:
return SeverityEnum.mild;
case 1:
return SeverityEnum.moderate;
case 2:
return SeverityEnum.severe;
default:
return null;
}
}
}
// Triage Level Enum
enum TriageLevelEnum { emergencyAmbulance, emergency, consultation24, consultation, selfCare }
extension TriageLevelEnumExtension on TriageLevelEnum {
int get toInt {
switch (this) {
case TriageLevelEnum.emergencyAmbulance:
return 0;
case TriageLevelEnum.emergency:
return 1;
case TriageLevelEnum.consultation24:
return 2;
case TriageLevelEnum.consultation:
return 3;
case TriageLevelEnum.selfCare:
return 4;
}
}
String get displayName {
AppState appState = getIt.get<AppState>();
bool isArabic = appState.getLanguageID() == 1 ? true : false;
switch (this) {
case TriageLevelEnum.emergencyAmbulance:
return isArabic ? 'طوارئ - إسعاف' : 'Emergency - Ambulance';
case TriageLevelEnum.emergency:
return isArabic ? 'طوارئ' : 'Emergency';
case TriageLevelEnum.consultation24:
return isArabic ? 'استشارة خلال 24 ساعة' : 'Consultation within 24 hours';
case TriageLevelEnum.consultation:
return isArabic ? 'استشارة' : 'Consultation';
case TriageLevelEnum.selfCare:
return isArabic ? 'رعاية ذاتية' : 'Self Care';
}
}
static TriageLevelEnum? fromInt(int value) {
switch (value) {
case 0:
return TriageLevelEnum.emergencyAmbulance;
case 1:
return TriageLevelEnum.emergency;
case 2:
return TriageLevelEnum.consultation24;
case 3:
return TriageLevelEnum.consultation;
case 4:
return TriageLevelEnum.selfCare;
default:
return null;
}
}
}
// Question Type Enum
enum QuestionTypeEnum { single, groupSingle, groupMultiple, duration }
extension QuestionTypeEnumExtension on QuestionTypeEnum {
int get toInt {
switch (this) {
case QuestionTypeEnum.single:
return 0;
case QuestionTypeEnum.groupSingle:
return 1;
case QuestionTypeEnum.groupMultiple:
return 2;
case QuestionTypeEnum.duration:
return 3;
}
}
String get displayName {
AppState appState = getIt.get<AppState>();
bool isArabic = appState.getLanguageID() == 1 ? true : false;
switch (this) {
case QuestionTypeEnum.single:
return isArabic ? 'سؤال واحد' : 'Single';
case QuestionTypeEnum.groupSingle:
return isArabic ? 'مجموعة - اختيار واحد' : 'Group Single';
case QuestionTypeEnum.groupMultiple:
return isArabic ? 'مجموعة - اختيار متعدد' : 'Group Multiple';
case QuestionTypeEnum.duration:
return isArabic ? 'المدة' : 'Duration';
}
}
static QuestionTypeEnum? fromInt(int value) {
switch (value) {
case 0:
return QuestionTypeEnum.single;
case 1:
return QuestionTypeEnum.groupSingle;
case 2:
return QuestionTypeEnum.groupMultiple;
case 3:
return QuestionTypeEnum.duration;
default:
return null;
}
}
}

@ -1,97 +1,97 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
class ConditionsModel {
final IconData icon;
final String title;
final int percentage;
final String tagText;
final String clinic;
final List<String> symptoms;
final String description;
final String? monitorNote;
final String? appointmentLabel;
final PossibleConditionsSeverityEnum possibleConditionsSeverityEnum;
ConditionsModel({
required this.icon,
required this.title,
required this.percentage,
required this.tagText,
required this.clinic,
required this.symptoms,
required this.description,
required this.possibleConditionsSeverityEnum,
this.monitorNote,
this.appointmentLabel,
});
}
List<ConditionsModel> dummyConditions = [
ConditionsModel(
icon: Icons.psychology_alt,
possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.seekMedicalAdvice,
title: "Migraine",
percentage: 87,
tagText: "Seek Medical Advice",
clinic: "Internal Medicine Clinic",
symptoms: ["Headache", "Nausea", "Sensitivity to light"],
description: "A migraine is a type of headache that can cause severe throbbing pain, usually on one side of the head.",
appointmentLabel: "Book Appointment",
),
ConditionsModel(
icon: Icons.deblur,
title: "Tension Headache",
percentage: 37,
tagText: "Monitor",
monitorNote: "No need to visit doctor",
clinic: "GP Clinic",
symptoms: ["Mild head pressure", "Scalp tenderness"],
description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.",
possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.monitorOnly,
),
ConditionsModel(
icon: Icons.medication_liquid,
title: "Meningitis",
percentage: 28,
tagText: "Emergency",
clinic: "Neurology Clinic",
symptoms: ["Mild head pressure", "Scalp tenderness"],
description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.",
appointmentLabel: "Book Appointment",
possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.emergency,
),
ConditionsModel(
icon: Icons.psychology_alt,
title: "Migraine",
percentage: 87,
tagText: "Seek Medical Advice",
clinic: "Internal Medicine Clinic",
symptoms: ["Headache", "Nausea", "Sensitivity to light"],
description: "A migraine is a type of headache that can cause severe throbbing pain, usually on one side of the head.",
appointmentLabel: "Book Appointment",
possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.seekMedicalAdvice,
),
ConditionsModel(
icon: Icons.deblur,
title: "Tension Headache",
percentage: 37,
tagText: "Monitor",
monitorNote: "No need to visit doctor",
clinic: "GP Clinic",
symptoms: ["Mild head pressure", "Scalp tenderness"],
description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.",
possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.monitorOnly,
),
ConditionsModel(
icon: Icons.medication_liquid,
title: "Meningitis",
percentage: 28,
tagText: "Emergency",
clinic: "Neurology Clinic",
symptoms: ["Mild head pressure", "Scalp tenderness"],
description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.",
appointmentLabel: "Book Appointment",
possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.emergency,
),
];
// import 'package:flutter/material.dart';
// import 'package:hmg_patient_app_new/core/enums.dart';
//
// class ConditionsModel {
// final IconData icon;
// final String title;
// final int percentage;
// final String tagText;
// final String clinic;
// final List<String> symptoms;
// final String description;
// final String? monitorNote;
// final String? appointmentLabel;
// final PossibleConditionsSeverityEnum possibleConditionsSeverityEnum;
//
// ConditionsModel({
// required this.icon,
// required this.title,
// required this.percentage,
// required this.tagText,
// required this.clinic,
// required this.symptoms,
// required this.description,
// required this.possibleConditionsSeverityEnum,
// this.monitorNote,
// this.appointmentLabel,
// });
// }
//
// List<ConditionsModel> dummyConditions = [
// ConditionsModel(
// icon: Icons.psychology_alt,
// possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.seekMedicalAdvice,
// title: "Migraine",
// percentage: 87,
// tagText: "Seek Medical Advice",
// clinic: "Internal Medicine Clinic",
// symptoms: ["Headache", "Nausea", "Sensitivity to light"],
// description: "A migraine is a type of headache that can cause severe throbbing pain, usually on one side of the head.",
// appointmentLabel: "Book Appointment",
// ),
// ConditionsModel(
// icon: Icons.deblur,
// title: "Tension Headache",
// percentage: 37,
// tagText: "Monitor",
// monitorNote: "No need to visit doctor",
// clinic: "GP Clinic",
// symptoms: ["Mild head pressure", "Scalp tenderness"],
// description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.",
// possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.monitorOnly,
// ),
// ConditionsModel(
// icon: Icons.medication_liquid,
// title: "Meningitis",
// percentage: 28,
// tagText: "Emergency",
// clinic: "Neurology Clinic",
// symptoms: ["Mild head pressure", "Scalp tenderness"],
// description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.",
// appointmentLabel: "Book Appointment",
// possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.emergency,
// ),
// ConditionsModel(
// icon: Icons.psychology_alt,
// title: "Migraine",
// percentage: 87,
// tagText: "Seek Medical Advice",
// clinic: "Internal Medicine Clinic",
// symptoms: ["Headache", "Nausea", "Sensitivity to light"],
// description: "A migraine is a type of headache that can cause severe throbbing pain, usually on one side of the head.",
// appointmentLabel: "Book Appointment",
// possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.seekMedicalAdvice,
// ),
// ConditionsModel(
// icon: Icons.deblur,
// title: "Tension Headache",
// percentage: 37,
// tagText: "Monitor",
// monitorNote: "No need to visit doctor",
// clinic: "GP Clinic",
// symptoms: ["Mild head pressure", "Scalp tenderness"],
// description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.",
// possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.monitorOnly,
// ),
// ConditionsModel(
// icon: Icons.medication_liquid,
// title: "Meningitis",
// percentage: 28,
// tagText: "Emergency",
// clinic: "Neurology Clinic",
// symptoms: ["Mild head pressure", "Scalp tenderness"],
// description: "A tension-type headache is generally a mild to moderate pain that feels like a tight band around your head.",
// appointmentLabel: "Book Appointment",
// possibleConditionsSeverityEnum: PossibleConditionsSeverityEnum.emergency,
// ),
// ];

@ -1,3 +1,5 @@
import 'package:hmg_patient_app_new/core/enums.dart';
class TriageDataDetails {
final TriageQuestion? question;
final List<TriageCondition>? conditions;
@ -171,7 +173,7 @@ class TriageCondition {
final String? name;
final String? commonName;
final double? probability;
final dynamic conditionDetails;
final ConditionDetails? conditionDetails;
TriageCondition({
this.id,
@ -187,7 +189,7 @@ class TriageCondition {
name: json['name'],
commonName: json['common_name'],
probability: json['probability']?.toDouble(),
conditionDetails: json['condition_details'],
conditionDetails: json['condition_details'] != null ? ConditionDetails.fromJson(json['condition_details']) : null,
);
}
@ -197,7 +199,7 @@ class TriageCondition {
'name': name,
'common_name': commonName,
'probability': probability,
'condition_details': conditionDetails,
'condition_details': conditionDetails?.toJson(),
};
}
@ -207,3 +209,75 @@ class TriageCondition {
return '${(probability! * 100).toStringAsFixed(1)}%';
}
}
class ConditionDetails {
final String? icd10Code;
final ConditionCategory? category;
final SeverityEnum? severity;
final TriageLevelEnum? triageLevel;
final String? hint;
final bool? hasPatientEducation;
final String? prevalence;
final String? acuteness;
ConditionDetails({
this.icd10Code,
this.category,
this.severity,
this.triageLevel,
this.hint,
this.hasPatientEducation,
this.prevalence,
this.acuteness,
});
factory ConditionDetails.fromJson(Map<String, dynamic> json) {
return ConditionDetails(
icd10Code: json['icd10_code'],
category: json['category'] != null ? ConditionCategory.fromJson(json['category']) : null,
severity: json['severity'] != null ? SeverityEnumExtension.fromInt(json['severity']) : null,
triageLevel: json['triage_level'] != null ? TriageLevelEnumExtension.fromInt(json['triage_level']) : null,
hint: json['hint'],
hasPatientEducation: json['has_patient_education'],
prevalence: json['prevalence'],
acuteness: json['acuteness'],
);
}
Map<String, dynamic> toJson() {
return {
'icd10_code': icd10Code,
'category': category?.toJson(),
'severity': severity?.toInt,
'triage_level': triageLevel?.toInt,
'hint': hint,
'has_patient_education': hasPatientEducation,
'prevalence': prevalence,
'acuteness': acuteness,
};
}
}
class ConditionCategory {
final String? id;
final String? name;
ConditionCategory({
this.id,
this.name,
});
factory ConditionCategory.fromJson(Map<String, dynamic> json) {
return ConditionCategory(
id: json['id'],
name: json['name'],
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
};
}
}

@ -12,50 +12,42 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models
import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class SymptomsCheckerRepo {
Future<Either<Failure, GenericApiModel<SymptomsUserDetailsResponseModel>>>
getUserDetails({
Future<Either<Failure, GenericApiModel<SymptomsUserDetailsResponseModel>>> getUserDetails({
required String userName,
required String password,
});
Future<Either<Failure, GenericApiModel<BodySymptomResponseModel>>>
getBodySymptomsByName({
Future<Either<Failure, GenericApiModel<BodySymptomResponseModel>>> getBodySymptomsByName({
required List<String> organNames,
required String userSessionToken,
required int gender,
});
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>>
getRiskFactors({
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>> getRiskFactors({
required int age,
required String sex,
required int gender,
required List<String> evidenceIds,
required String language,
required String userSessionToken,
required int gender,
required String sessionId,
});
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>>
getSuggestions({
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>> getSuggestions({
required int age,
required String sex,
required int gender,
required List<String> evidenceIds,
required String language,
required String userSessionToken,
required String sessionId,
required int gender,
});
Future<Either<Failure, GenericApiModel<TriageDataDetails>>>
getDiagnosisForTriage({
Future<Either<Failure, GenericApiModel<TriageDataDetails>>> getDiagnosisForTriage({
required int age,
required String sex,
required int gender,
required List<String> evidenceIds,
List<Map<String, String>>? triageEvidence,
required String language,
required String userSessionToken,
required int gender,
required String sessionId,
});
}
@ -64,12 +56,10 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
final ApiClient apiClient;
final LoggerService loggerService;
SymptomsCheckerRepoImp(
{required this.apiClient, required this.loggerService});
SymptomsCheckerRepoImp({required this.apiClient, required this.loggerService});
@override
Future<Either<Failure, GenericApiModel<SymptomsUserDetailsResponseModel>>>
getUserDetails({
Future<Either<Failure, GenericApiModel<SymptomsUserDetailsResponseModel>>> getUserDetails({
required String userName,
required String password,
}) async {
@ -92,11 +82,9 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
// Parse response if it's a string
final Map<String, dynamic> responseData =
response is String ? jsonDecode(response) : response;
final Map<String, dynamic> responseData = response is String ? jsonDecode(response) : response;
SymptomsUserDetailsResponseModel symptomsUserDetailsResponseModel =
SymptomsUserDetailsResponseModel.fromJson(responseData);
SymptomsUserDetailsResponseModel symptomsUserDetailsResponseModel = SymptomsUserDetailsResponseModel.fromJson(responseData);
apiResponse = GenericApiModel<SymptomsUserDetailsResponseModel>(
messageStatus: messageStatus ?? 1,
@ -123,8 +111,7 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
}
@override
Future<Either<Failure, GenericApiModel<BodySymptomResponseModel>>>
getBodySymptomsByName({
Future<Either<Failure, GenericApiModel<BodySymptomResponseModel>>> getBodySymptomsByName({
required List<String> organNames,
required String userSessionToken,
required int gender,
@ -154,8 +141,7 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
BodySymptomResponseModel bodySymptomResponse =
BodySymptomResponseModel.fromJson(response);
BodySymptomResponseModel bodySymptomResponse = BodySymptomResponseModel.fromJson(response);
apiResponse = GenericApiModel<BodySymptomResponseModel>(
messageStatus: messageStatus ?? 1,
@ -164,8 +150,7 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
data: bodySymptomResponse,
);
} catch (e, stackTrace) {
loggerService
.logError("Error parsing GetBodySymptomsByName response: $e");
loggerService.logError("Error parsing GetBodySymptomsByName response: $e");
loggerService.logError("StackTrace: $stackTrace");
failure = DataParsingFailure(e.toString());
}
@ -183,21 +168,19 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
}
@override
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>>
getRiskFactors({
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>> getRiskFactors({
required int age,
required String sex,
required int gender,
required List<String> evidenceIds,
required String language,
required String userSessionToken,
required int gender,
required String sessionId,
}) async {
final Map<String, dynamic> body = {
"age": {
"value": age,
},
"sex": sex,
"gender": gender,
"evidence": evidenceIds.map((id) => {"id": id}).toList(),
"language": language,
"generalId": sessionId,
@ -225,11 +208,9 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
// Parse response if it's a string
final Map<String, dynamic> responseData =
response is String ? jsonDecode(response) : response;
final Map<String, dynamic> responseData = response is String ? jsonDecode(response) : response;
RiskAndSuggestionsResponseModel riskFactorsResponse =
RiskAndSuggestionsResponseModel.fromJson(responseData);
RiskAndSuggestionsResponseModel riskFactorsResponse = RiskAndSuggestionsResponseModel.fromJson(responseData);
apiResponse = GenericApiModel<RiskAndSuggestionsResponseModel>(
messageStatus: messageStatus ?? 1,
@ -255,16 +236,14 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
}
}
Future<Either<Failure, GenericApiModel<TriageDataDetails>>>
getDiagnosisForTriage({
@override
Future<Either<Failure, GenericApiModel<TriageDataDetails>>> getDiagnosisForTriage({
required int age,
required String sex,
required int gender,
required List<String> evidenceIds,
List<Map<String, String>>?
triageEvidence, // Additional triage-specific evidence
List<Map<String, String>>? triageEvidence, // Additional triage-specific evidence
required String language,
required String userSessionToken,
required int gender,
required String sessionId,
}) async {
// Build evidence list: combine initial symptoms with triage evidence
@ -284,7 +263,7 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
"age": {
"value": age,
},
"sex": sex,
"gender": gender,
"evidence": evidenceList,
"language": language,
"suggest_method": "diagnosis",
@ -313,13 +292,11 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
// Parse response if it's a string
final Map<String, dynamic> responseData =
response is String ? jsonDecode(response) : response;
final Map<String, dynamic> responseData = response is String ? jsonDecode(response) : response;
final updatedResponseData = responseData['dataDetails'];
TriageDataDetails riskFactorsResponse =
TriageDataDetails.fromJson(updatedResponseData);
TriageDataDetails riskFactorsResponse = TriageDataDetails.fromJson(updatedResponseData);
apiResponse = GenericApiModel<TriageDataDetails>(
messageStatus: messageStatus ?? 1,
@ -328,8 +305,7 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
data: riskFactorsResponse,
);
} catch (e, stackTrace) {
loggerService
.logError("Error parsing getDiagnosisForTriage response: $e");
loggerService.logError("Error parsing getDiagnosisForTriage response: $e");
loggerService.logError("StackTrace: $stackTrace");
failure = DataParsingFailure(e.toString());
}
@ -347,21 +323,19 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
}
@override
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>>
getSuggestions({
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>> getSuggestions({
required int age,
required String sex,
required int gender,
required List<String> evidenceIds,
required String language,
required String userSessionToken,
required String sessionId,
required int gender,
}) async {
final Map<String, dynamic> body = {
"age": {
"value": age,
},
"sex": sex,
"gender": gender,
"evidence": evidenceIds.map((id) => {"id": id}).toList(),
"language": language,
"generalId": sessionId,
@ -389,11 +363,9 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
// Parse response if it's a string
final Map<String, dynamic> responseData =
response is String ? jsonDecode(response) : response;
final Map<String, dynamic> responseData = response is String ? jsonDecode(response) : response;
RiskAndSuggestionsResponseModel riskFactorsResponse =
RiskAndSuggestionsResponseModel.fromJson(responseData);
RiskAndSuggestionsResponseModel riskFactorsResponse = RiskAndSuggestionsResponseModel.fromJson(responseData);
apiResponse = GenericApiModel<RiskAndSuggestionsResponseModel>(
messageStatus: messageStatus ?? 1,

@ -1,6 +1,6 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/data/organ_mapping_data.dart';
@ -47,12 +47,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
TriageDataDetails? triageDataDetails;
// Triage state
int?
_selectedTriageChoiceIndex; // Deprecated - keeping for backward compatibility
final Map<String, int> _selectedTriageChoicesByItemId =
{}; // Map of itemId -> choiceIndex for multi-item questions
final List<Map<String, String>> _triageEvidenceList =
[]; // Store triage evidence with proper format
int? _selectedTriageChoiceIndex; // Deprecated - keeping for backward compatibility
final Map<String, int> _selectedTriageChoicesByItemId = {}; // Map of itemId -> choiceIndex for multi-item questions
final List<Map<String, String>> _triageEvidenceList = []; // Store triage evidence with proper format
int _triageQuestionCount = 0; // Track number of triage questions answered
// Selected risk factors tracking
@ -66,8 +63,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
// User Info Flow State
int _userInfoCurrentPage = 0;
bool _isSinglePageEditMode =
false; // Track if editing single page or full flow
bool _isSinglePageEditMode = false; // Track if editing single page or full flow
String? _selectedGender;
DateTime? _dateOfBirth;
int? _selectedAge;
@ -107,17 +103,14 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
String? get tooltipOrganId => _tooltipOrganId;
String get currentSessionAuthToken =>
symptomsUserDetailsResponseModel?.tokenDetails?.authToken ?? "";
String get currentSessionAuthToken => symptomsUserDetailsResponseModel?.tokenDetails?.authToken ?? "";
String get currentSessionId =>
symptomsUserDetailsResponseModel?.sessionId ?? "";
String get currentSessionId => symptomsUserDetailsResponseModel?.sessionId ?? "";
// Triage-related getters
bool get shouldStopTriage => triageDataDetails?.shouldStop ?? false;
bool get hasEmergencyEvidence =>
triageDataDetails?.hasEmergencyEvidence ?? false;
bool get hasEmergencyEvidence => triageDataDetails?.hasEmergencyEvidence ?? false;
String? get currentInterviewToken => triageDataDetails?.interviewToken;
@ -137,15 +130,13 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
/// Check if all items in current question have been answered
bool get areAllTriageItemsAnswered {
if (currentTriageQuestion?.items == null ||
currentTriageQuestion!.items!.isEmpty) {
if (currentTriageQuestion?.items == null || currentTriageQuestion!.items!.isEmpty) {
return false;
}
// Check if we have an answer for each item
for (var item in currentTriageQuestion!.items!) {
if (item.id != null &&
!_selectedTriageChoicesByItemId.containsKey(item.id)) {
if (item.id != null && !_selectedTriageChoicesByItemId.containsKey(item.id)) {
return false;
}
}
@ -153,8 +144,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
}
/// Get organs for current view
List<OrganModel> get currentOrgans =>
OrganData.getOrgansForView(_currentView);
List<OrganModel> get currentOrgans => OrganData.getOrgansForView(_currentView);
/// Get all selected organs from both views
List<OrganModel> get selectedOrgans {
@ -162,9 +152,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
...OrganData.frontViewOrgans,
...OrganData.backViewOrgans,
];
return allOrgans
.where((organ) => _selectedOrganIds.contains(organ.id))
.toList();
return allOrgans.where((organ) => _selectedOrganIds.contains(organ.id)).toList();
}
/// Check if any organs are selected
@ -181,13 +169,11 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
}
int get totalSelectedSymptomsCount {
return _selectedSymptomsByOrgan.values
.fold(0, (sum, symptomIds) => sum + symptomIds.length);
return _selectedSymptomsByOrgan.values.fold(0, (sum, symptomIds) => sum + symptomIds.length);
}
bool get hasSelectedSymptoms {
return _selectedSymptomsByOrgan.values
.any((symptomIds) => symptomIds.isNotEmpty);
return _selectedSymptomsByOrgan.values.any((symptomIds) => symptomIds.isNotEmpty);
}
/// Get risk factors list
@ -213,8 +199,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
}
void toggleView() {
_currentView =
_currentView == BodyView.front ? BodyView.back : BodyView.front;
_currentView = _currentView == BodyView.front ? BodyView.back : BodyView.front;
notifyListeners();
}
@ -317,8 +302,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
return;
}
List<String> organNames =
selectedOrgans.map((organ) => organ.name).toList();
List<String> organNames = selectedOrgans.map((organ) => organ.name).toList();
await getBodySymptomsByName(
organNames: organNames,
@ -368,8 +352,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
}
}
if (matchingOrganId != null &&
_selectedSymptomsByOrgan.containsKey(matchingOrganId)) {
if (matchingOrganId != null && _selectedSymptomsByOrgan.containsKey(matchingOrganId)) {
final selectedIds = _selectedSymptomsByOrgan[matchingOrganId]!;
if (organResult.bodySymptoms != null) {
@ -420,10 +403,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
/// Get all selected risk factors
List<RiskAndSuggestionsItemModel> getAllSelectedRiskFactors() {
return riskFactorsList
.where((factor) =>
factor.id != null && _selectedRiskFactorIds.contains(factor.id))
.toList();
return riskFactorsList.where((factor) => factor.id != null && _selectedRiskFactorIds.contains(factor.id)).toList();
}
/// Clear all risk factor selections
@ -456,12 +436,11 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
}
// Extract symptom IDs
List<String> evidenceIds =
selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList();
List<String> evidenceIds = selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList();
await getRiskFactors(
age: _selectedAge!,
sex: _selectedGender!.toLowerCase(),
gender: _selectedGender!.toLowerCase() == "male" ? 1 : 2,
evidenceIds: evidenceIds,
sessionId: currentSessionId,
language: appState.isArabic() ? 'ar' : 'en',
@ -481,7 +460,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
/// Call Risk Factors API
Future<void> getRiskFactors({
required int age,
required String sex,
required int gender,
required String sessionId,
required List<String> evidenceIds,
required String language,
@ -493,12 +472,11 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
final result = await symptomsCheckerRepo.getRiskFactors(
age: age,
sex: sex,
gender: gender,
evidenceIds: evidenceIds,
language: language,
sessionId: sessionId,
userSessionToken: currentSessionAuthToken,
gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2,
);
result.fold(
@ -515,10 +493,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
if (apiResponse.messageStatus == 1 && apiResponse.data != null) {
riskFactorsResponse = apiResponse.data;
if (riskFactorsResponse != null &&
riskFactorsResponse!.dataDetails != null) {
RiskAndSuggestionsItemModel riskFactorItem =
RiskAndSuggestionsItemModel(
if (riskFactorsResponse != null && riskFactorsResponse!.dataDetails != null) {
RiskAndSuggestionsItemModel riskFactorItem = RiskAndSuggestionsItemModel(
id: "not_applicable",
commonName: "Not Applicable",
name: "Not Applicable",
@ -572,10 +548,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
/// Get all selected risk factors
List<RiskAndSuggestionsItemModel> getAllSelectedSuggestions() {
return suggestionsList
.where((factor) =>
factor.id != null && _selectedSuggestionsIds.contains(factor.id))
.toList();
return suggestionsList.where((factor) => factor.id != null && _selectedSuggestionsIds.contains(factor.id)).toList();
}
/// Clear all risk factor selections
@ -590,20 +563,15 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
// Add selected symptoms
final selectedSymptoms = getAllSelectedSymptoms();
evidenceIds
.addAll(selectedSymptoms.where((s) => s.id != null).map((s) => s.id!));
evidenceIds.addAll(selectedSymptoms.where((s) => s.id != null).map((s) => s.id!));
// Add selected risk factors (excluding "not_applicable")
final selectedRiskFactors = getAllSelectedRiskFactors();
evidenceIds.addAll(selectedRiskFactors
.where((rf) => rf.id != null && rf.id != "not_applicable")
.map((rf) => rf.id!));
evidenceIds.addAll(selectedRiskFactors.where((rf) => rf.id != null && rf.id != "not_applicable").map((rf) => rf.id!));
// Add selected suggestions (excluding "not_applicable")
final selectedSuggestions = getAllSelectedSuggestions();
evidenceIds.addAll(selectedSuggestions
.where((s) => s.id != null && s.id != "not_applicable")
.map((s) => s.id!));
evidenceIds.addAll(selectedSuggestions.where((s) => s.id != null && s.id != "not_applicable").map((s) => s.id!));
return evidenceIds;
}
@ -632,23 +600,19 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
}
// Extract symptom IDs
List<String> evidenceIds =
selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList();
List<String> evidenceIds = selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList();
// Get all selected symptoms
final selectedRisks = getAllSelectedRiskFactors();
if (selectedRisks.isNotEmpty) {
List<String> evidenceRisksIds = selectedRisks
.where((s) => s.id != null && s.id != "not_applicable")
.map((s) => s.id!)
.toList();
List<String> evidenceRisksIds = selectedRisks.where((s) => s.id != null && s.id != "not_applicable").map((s) => s.id!).toList();
evidenceIds.addAll(evidenceRisksIds);
}
await getSuggestions(
age: _selectedAge!,
sex: _selectedGender!.toLowerCase(),
gender: _selectedGender!.toLowerCase() == "male" ? 1 : 2,
evidenceIds: evidenceIds,
language: appState.isArabic() ? 'ar' : 'en',
onSuccess: (response) {
@ -667,7 +631,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
/// Call Suggestions API
Future<void> getSuggestions({
required int age,
required String sex,
required int gender,
required List<String> evidenceIds,
required String language,
Function(RiskAndSuggestionsResponseModel)? onSuccess,
@ -678,12 +642,11 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
final result = await symptomsCheckerRepo.getSuggestions(
age: age,
sex: sex,
gender: gender,
evidenceIds: evidenceIds,
language: language,
sessionId: currentSessionId,
userSessionToken: currentSessionAuthToken,
gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2,
);
result.fold(
@ -700,10 +663,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
if (apiResponse.messageStatus == 1 && apiResponse.data != null) {
suggestionsResponse = apiResponse.data;
if (suggestionsResponse != null &&
suggestionsResponse!.dataDetails != null) {
RiskAndSuggestionsItemModel riskFactorItem =
RiskAndSuggestionsItemModel(
if (suggestionsResponse != null && suggestionsResponse!.dataDetails != null) {
RiskAndSuggestionsItemModel riskFactorItem = RiskAndSuggestionsItemModel(
id: "not_applicable",
commonName: "Not Applicable",
name: "Not Applicable",
@ -730,7 +691,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
/// Call Diagnosis API for Triage - This is called iteratively until shouldStop is true
Future<void> getDiagnosisForTriage({
required int age,
required String sex,
required int gender,
required List<String> evidenceIds,
List<Map<String, String>>? triageEvidence,
required String language,
@ -742,13 +703,12 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
final result = await symptomsCheckerRepo.getDiagnosisForTriage(
age: age,
sex: sex,
gender: gender,
evidenceIds: evidenceIds,
triageEvidence: triageEvidence,
language: language,
sessionId: currentSessionId,
userSessionToken: currentSessionAuthToken,
gender: (selectedGender ?? "Male").toLowerCase() == "male" ? 1 : 2,
);
result.fold(
@ -804,7 +764,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
await getDiagnosisForTriage(
age: _selectedAge!,
sex: _selectedGender!.toLowerCase(),
gender: _selectedGender!.toLowerCase() == "male" ? 1 : 2,
evidenceIds: evidenceIds,
language: appState.isArabic() ? 'ar' : 'en',
onSuccess: (response) {
@ -941,8 +901,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
// Calculate age from date of birth
final now = DateTime.now();
int age = now.year - dateOfBirth.year;
if (now.month < dateOfBirth.month ||
(now.month == dateOfBirth.month && now.day < dateOfBirth.day)) {
if (now.month < dateOfBirth.month || (now.month == dateOfBirth.month && now.day < dateOfBirth.day)) {
age--;
}
_selectedAge = age;
@ -989,8 +948,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
}) async {
isBodySymptomsLoading = true;
notifyListeners();
final result = await symptomsCheckerRepo.getUserDetails(
userName: userName, password: password);
final result = await symptomsCheckerRepo.getUserDetails(userName: userName, password: password);
result.fold(
(failure) async {

@ -50,7 +50,14 @@ class ServicesPage extends StatelessWidget {
late MedicalFileViewModel medicalFileViewModel;
late final List<HmgServicesComponentModel> hmgServices = [
HmgServicesComponentModel(11, LocaleKeys.emergencyServices.tr(), "", AppAssets.emergency_services_icon, bgColor: AppColors.primaryRedColor, true, route: null, onTap: () async {
HmgServicesComponentModel(
11,
LocaleKeys.emergencyServices.tr(),
"",
AppAssets.emergency_services_icon,
bgColor: AppColors.primaryRedColor,
true,
route: null, onTap: () async {
if (getIt.get<AppState>().isAuthenticated) {
getIt.get<EmergencyServicesViewModel>().flushData();
getIt.get<EmergencyServicesViewModel>().getTransportationOrders(
@ -59,7 +66,7 @@ class ServicesPage extends StatelessWidget {
getIt.get<EmergencyServicesViewModel>().getRRTOrders(
showLoader: false,
);
Navigator.of(GetIt.instance<NavigationService>().navigatorKey.currentContext!).push(
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).push(
CustomPageRoute(
page: EmergencyServicesPage(),
settings: const RouteSettings(name: '/EmergencyServicesPage'),
@ -78,7 +85,8 @@ class ServicesPage extends StatelessWidget {
true,
route: AppRoutes.bookAppointmentPage,
),
HmgServicesComponentModel(5, LocaleKeys.completeCheckup.tr(), "", AppAssets.comprehensiveCheckup, bgColor: AppColors.bgGreenColor, true, route: null, onTap: () async {
HmgServicesComponentModel(
5, LocaleKeys.completeCheckup.tr(), "", AppAssets.comprehensiveCheckup, bgColor: AppColors.bgGreenColor, true, route: null, onTap: () async {
if (getIt.get<AppState>().isAuthenticated) {
getIt.get<NavigationService>().pushPageRoute(AppRoutes.comprehensiveCheckupPage);
} else {
@ -131,7 +139,7 @@ class ServicesPage extends StatelessWidget {
},
),
HmgServicesComponentModel(
11, LocaleKeys.eReferralServices.tr(), "", AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async {
11, LocaleKeys.eReferralServices.tr(), "", AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async {
if (getIt.get<AppState>().isAuthenticated) {
getIt.get<NavigationService>().pushPageRoute(AppRoutes.eReferralPage);
} else {
@ -393,19 +401,19 @@ class ServicesPage extends StatelessWidget {
? CustomButton(
height: 40.h,
icon: AppAssets.recharge_icon,
iconSize: 16.w,
iconColor: AppColors.infoColor,
textColor: AppColors.infoColor,
text: LocaleKeys.recharge.tr(),
borderWidth: 0.w,
fontWeight: FontWeight.w500,
borderColor: Colors.transparent,
backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08),
padding: EdgeInsets.all(8.w),
fontSize: 12.f,
onPressed: () {
Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage()));
},
iconSize: 16.w,
iconColor: AppColors.infoColor,
textColor: AppColors.infoColor,
text: LocaleKeys.recharge.tr(),
borderWidth: 0.w,
fontWeight: FontWeight.w500,
borderColor: Colors.transparent,
backgroundColor: Color(0xff45A2F8).withValues(alpha: 0.08),
padding: EdgeInsets.all(8.w),
fontSize: 12.f,
onPressed: () {
Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage()));
},
)
: SizedBox.shrink(),
],
@ -477,27 +485,27 @@ class ServicesPage extends StatelessWidget {
getIt.get<AppState>().isAuthenticated
? CustomButton(
height: 40.h,
icon: AppAssets.add_icon,
iconSize: 16.w,
iconColor: AppColors.primaryRedColor,
textColor: AppColors.primaryRedColor,
text: LocaleKeys.addMember.tr(),
borderWidth: 0.w,
fontWeight: FontWeight.w500,
borderColor: Colors.transparent,
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.08),
padding: EdgeInsets.all(8.w),
fontSize: 12.f,
onPressed: () {
DialogService dialogService = getIt.get<DialogService>();
medicalFileViewModel.clearAuthValues();
dialogService.showAddFamilyFileSheet(
label: LocaleKeys.addFamilyMember.tr(),
message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(),
onVerificationPress: () {
medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms);
});
},
icon: AppAssets.add_icon,
iconSize: 16.w,
iconColor: AppColors.primaryRedColor,
textColor: AppColors.primaryRedColor,
text: LocaleKeys.addMember.tr(),
borderWidth: 0.w,
fontWeight: FontWeight.w500,
borderColor: Colors.transparent,
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.08),
padding: EdgeInsets.all(8.w),
fontSize: 12.f,
onPressed: () {
DialogService dialogService = getIt.get<DialogService>();
medicalFileViewModel.clearAuthValues();
dialogService.showAddFamilyFileSheet(
label: LocaleKeys.addFamilyMember.tr(),
message: LocaleKeys.pleaseFillBelowFieldToAddNewFamilyMember.tr(),
onVerificationPress: () {
medicalFileViewModel.addFamilyFile(otpTypeEnum: OTPTypeEnum.sms);
});
},
)
: SizedBox.shrink(),
],

@ -28,7 +28,6 @@ import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/appointment_queue_page.dart';
@ -62,8 +61,6 @@ import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:hmg_patient_app_new/widgets/routes/spring_page_route_builder.dart';
import 'package:provider/provider.dart';
import '../active_medication/active_medication_page.dart';
import '../emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart';
class LandingPage extends StatefulWidget {
@ -394,22 +391,23 @@ class _LandingPageState extends State<LandingPage> {
layout: SwiperLayout.STACK,
loop: true,
itemWidth: MediaQuery.of(context).size.width - 48.h,
indicatorLayout: PageIndicatorLayout.COLOR,
axisDirection: AxisDirection.right,
controller: _controller,
itemHeight: 255.h,
pagination: SwiperPagination(
alignment: Alignment.bottomCenter,
margin: EdgeInsets.only(top: 240.h + 8 + 24),
builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor),
),
itemBuilder: (BuildContext context, int index) {
return getIndexSwiperCard(index);
},
)
indicatorLayout: PageIndicatorLayout.COLOR,
axisDirection: AxisDirection.right,
controller: _controller,
itemHeight: 255.h,
pagination: SwiperPagination(
alignment: Alignment.bottomCenter,
margin: EdgeInsets.only(top: 240.h + 8 + 24),
builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor),
),
itemBuilder: (BuildContext context, int index) {
return getIndexSwiperCard(index);
},
)
: Container(
width: double.infinity,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
@ -469,7 +467,11 @@ class _LandingPageState extends State<LandingPage> {
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.10),
textColor: AppColors.primaryRedColor,
),
Utils.buildSvgWithAssets(icon: AppAssets.appointment_checkin_icon, width: 24.h, height: 24.h, iconColor: AppColors.primaryRedColor),
Utils.buildSvgWithAssets(
icon: AppAssets.appointment_checkin_icon,
width: 24.h,
height: 24.h,
iconColor: AppColors.primaryRedColor),
],
),
SizedBox(height: 8.h),
@ -690,10 +692,18 @@ class _LandingPageState extends State<LandingPage> {
myAppointmentsViewModel.patientQueueDetailsList.first.queueNo!.toText12(isBold: true),
SizedBox(width: 8.w),
AppCustomChipWidget(
deleteIcon: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? AppAssets.call_for_vitals : AppAssets.call_for_doctor,
labelText: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? LocaleKeys.callForVitalSigns.tr() : LocaleKeys.callForDoctor.tr(),
iconColor: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? AppColors.primaryRedColor : AppColors.successColor,
textColor: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1 ? AppColors.primaryRedColor : AppColors.successColor,
deleteIcon: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1
? AppAssets.call_for_vitals
: AppAssets.call_for_doctor,
labelText: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1
? LocaleKeys.callForVitalSigns.tr()
: LocaleKeys.callForDoctor.tr(),
iconColor: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1
? AppColors.primaryRedColor
: AppColors.successColor,
textColor: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1
? AppColors.primaryRedColor
: AppColors.successColor,
iconSize: 14.w,
backgroundColor: myAppointmentsViewModel.patientQueueDetailsList.first.callType == 1
? AppColors.primaryRedColor.withValues(alpha: 0.1)
@ -707,7 +717,8 @@ class _LandingPageState extends State<LandingPage> {
: SizedBox(height: 12.h),
SizedBox(height: 5.h),
CustomButton(
text: Utils.getCardButtonText(myAppointmentsViewModel.currentQueueStatus, myAppointmentsViewModel.currentPatientQueueDetails.roomNo ?? ""),
text: Utils.getCardButtonText(
myAppointmentsViewModel.currentQueueStatus, myAppointmentsViewModel.currentPatientQueueDetails.roomNo ?? ""),
onPressed: () {},
backgroundColor: Utils.getCardButtonColor(myAppointmentsViewModel.currentQueueStatus),
borderColor: Utils.getCardButtonColor(myAppointmentsViewModel.currentQueueStatus).withValues(alpha: 0.01),
@ -761,7 +772,8 @@ class _LandingPageState extends State<LandingPage> {
SizedBox(width: 8.w),
AppCustomChipWidget(
icon: AppAssets.appointment_calendar_icon,
labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(immediateLiveCareViewModel.patientLiveCareHistoryList[0].arrivalTime), false)),
labelText: DateUtil.formatDateToDate(
DateUtil.convertStringToDate(immediateLiveCareViewModel.patientLiveCareHistoryList[0].arrivalTime), false)),
],
),
Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h),
@ -769,9 +781,12 @@ class _LandingPageState extends State<LandingPage> {
],
),
SizedBox(height: 10.h),
LocaleKeys.halaFirstName.tr(namedArgs: {'firstName': appState.getAuthenticatedUser()!.firstName!}, context: context).toText16(isBold: true),
LocaleKeys.halaFirstName
.tr(namedArgs: {'firstName': appState.getAuthenticatedUser()!.firstName!}, context: context).toText16(isBold: true),
SizedBox(height: 8.h),
LocaleKeys.yourTurnIsAfterPatients.tr(namedArgs: {'count': immediateLiveCareViewModel.patientLiveCareHistoryList[0].patCount.toString()}, context: context).toText14(isBold: true),
LocaleKeys.yourTurnIsAfterPatients.tr(
namedArgs: {'count': immediateLiveCareViewModel.patientLiveCareHistoryList[0].patCount.toString()},
context: context).toText14(isBold: true),
SizedBox(height: 8.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -978,7 +993,6 @@ class _LandingPageState extends State<LandingPage> {
isCloseButtonVisible: true,
child: StatefulBuilder(
builder: (context, setState) {
return RateAppointmentDoctor();
},
),

@ -8,11 +8,8 @@ import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart';
import 'package:hmg_patient_app_new/presentation/hmg_services/services_page.dart';
import 'package:hmg_patient_app_new/presentation/home/landing_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/user_info_selection.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/widgets/bottom_navigation/bottom_navigation.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
class LandingNavigation extends StatefulWidget {
const LandingNavigation({super.key});
@ -53,11 +50,7 @@ class _LandingNavigationState extends State<LandingNavigation> {
}
if (_currentIndex == 3) {
if (appState.isAuthenticated) {
Navigator.of(context).push(
CustomPageRoute(
page: UserInfoSelectionScreen(),
),
);
context.navigateWithName(AppRoutes.userInfoSelection);
} else {
Utils.openWebView(
url: 'https://x.com/HMG',

@ -3,13 +3,14 @@ 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/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/models/conditions_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/condition_card.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
@ -45,7 +46,7 @@ class PossibleConditionsPage extends StatelessWidget {
);
}
Widget _buildPredictionsList(BuildContext context, List<ConditionsModel> conditions) {
Widget _buildPredictionsList(BuildContext context, List<TriageCondition> conditions, List<String> symptoms) {
if (conditions.isEmpty) {
return Center(
child: Padding(
@ -74,19 +75,13 @@ class PossibleConditionsPage extends StatelessWidget {
physics: NeverScrollableScrollPhysics(),
separatorBuilder: (context, index) => SizedBox(height: 16.h),
itemBuilder: (context, index) {
final conditionModel = conditions[index];
final condition = conditions[index];
return ConditionCard(
icon: conditionModel.icon,
title: conditionModel.title,
percentage: conditionModel.percentage,
tagText: conditionModel.tagText,
clinic: conditionModel.clinic,
symptoms: conditionModel.symptoms,
description: conditionModel.description,
possibleConditionsSeverityEnum: conditionModel.possibleConditionsSeverityEnum,
condition: condition,
symptoms: symptoms,
onActionPressed: () {
dialogService.showErrorBottomSheet(
message: 'We are not available for a week. May you Rest In Peace :(',
message: 'icd10 Code is ${condition.conditionDetails?.icd10Code}. We need to get the clinics mapped against this code.',
);
},
);
@ -102,7 +97,7 @@ class PossibleConditionsPage extends StatelessWidget {
title: LocaleKeys.notice.tr(context: context),
context,
child: Utils.getWarningWidget(
loadingText: LocaleKeys.areYouSureYouWantToRestartOrganSelection.tr(context: context),
loadingText: LocaleKeys.areYouSureYouWantToExitProgress.tr(context: context),
isShowActionButtons: true,
onCancelTap: () => Navigator.pop(context),
onConfirmTap: () => onConfirm(),
@ -113,47 +108,88 @@ class PossibleConditionsPage extends StatelessWidget {
);
}
_restartOrganSelection(BuildContext context) async {
final symptomsCheckerVm = context.read<SymptomsCheckerViewModel>();
symptomsCheckerVm.reset();
context.pop();
await Future.delayed(Duration(seconds: 1)).whenComplete(() => context.pop());
}
Widget _buildInfoTile({required PossibleConditionsSeverityEnum severityEnum}) {
String title;
String description;
Color dotColor;
_navigateToLandingPage() {
NavigationService navigationService = getIt.get<NavigationService>();
navigationService.replaceAllRoutesAndNavigateToLanding();
}
switch (severityEnum) {
case PossibleConditionsSeverityEnum.monitorOnly:
title = "Monitor".needTranslation;
description = "No need to seek medical advice. Just keep healthy routine.".needTranslation;
dotColor = AppColors.chipColorMonitor;
break;
case PossibleConditionsSeverityEnum.seekMedicalAdvice:
title = "Seek Medical Advice".needTranslation;
description = "Not emergency but better to monitor the symptoms.".needTranslation;
dotColor = AppColors.chipColorSeekMedicalAdvice;
break;
case PossibleConditionsSeverityEnum.emergency:
title = "Emergency".needTranslation;
description = "Need to consult doctor as soon as possible before getting too late.".needTranslation;
dotColor = AppColors.chipColorEmergency;
break;
}
_buildTrailingSection(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: 40.h,
padding: EdgeInsets.all(8.w),
child: Center(
child: Utils.buildSvgWithAssets(
icon: AppAssets.refreshIcon,
height: 20.h,
width: 20.w,
iconColor: AppColors.textColor,
),
height: 14.h,
width: 14.w,
decoration: BoxDecoration(shape: BoxShape.circle, color: dotColor),
).paddingOnly(top: 3.h),
SizedBox(width: 8.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
title.toText14(weight: FontWeight.w600, color: AppColors.textColor),
SizedBox(height: 4.h),
description.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor),
],
),
).onPress(() => _buildConfirmationBottomSheet(context: context, onConfirm: () => _restartOrganSelection(context))),
Container(
height: 40.h,
padding: EdgeInsets.all(8.w),
child: Center(
child: Utils.buildSvgWithAssets(
icon: AppAssets.homeBorderedIcon,
height: 20.h,
width: 20.w,
iconColor: AppColors.textColor,
),
),
).onPress(() => _buildConfirmationBottomSheet(context: context, onConfirm: () => _navigateToLandingPage())),
),
],
).paddingSymmetrical(20.w, 0);
);
}
_buildSeverityDetailsBottomsheet({required BuildContext context}) {
return showCommonBottomSheetWithoutHeight(
title: "Color Science".needTranslation,
context,
child: Container(
padding: EdgeInsets.all(16.w),
width: double.infinity,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
child: Column(
children: [
_buildInfoTile(severityEnum: PossibleConditionsSeverityEnum.monitorOnly),
Divider(color: AppColors.bottomNAVBorder, height: 24.h, thickness: 1),
_buildInfoTile(severityEnum: PossibleConditionsSeverityEnum.seekMedicalAdvice),
Divider(color: AppColors.bottomNAVBorder, height: 24.h, thickness: 1),
_buildInfoTile(severityEnum: PossibleConditionsSeverityEnum.emergency),
],
),
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
_buildTrailingSection(BuildContext context) {
return Container(
height: 40.h,
padding: EdgeInsets.all(8.w),
child: Center(
child: Utils.buildSvgWithAssets(
icon: AppAssets.coloredDotsIcon,
height: 20.h,
width: 20.w,
),
),
).onPress(() => _buildSeverityDetailsBottomsheet(context: context));
}
@override
@ -162,13 +198,32 @@ class PossibleConditionsPage extends StatelessWidget {
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
title: LocaleKeys.possibleConditions.tr(context: context),
leadingCallback: () => _buildConfirmationBottomSheet(
context: context,
onConfirm: () {
context.pop();
final SymptomsCheckerViewModel symptomsCheckerViewModel = context.read<SymptomsCheckerViewModel>();
symptomsCheckerViewModel.reset(); // Clear all symptoms checker data
final navigationService = getIt.get<NavigationService>();
navigationService.replaceAllRoutesAndNavigateToLanding();
},
),
trailing: _buildTrailingSection(context),
child: Consumer<SymptomsCheckerViewModel>(
builder: (context, symptomsCheckerViewModel, child) {
if (symptomsCheckerViewModel.isPossibleConditionsLoading || symptomsCheckerViewModel.isPossibleConditionsLoading) {
if (symptomsCheckerViewModel.isPossibleConditionsLoading || symptomsCheckerViewModel.isTriageDiagnosisLoading) {
return _buildLoadingShimmer();
}
return _buildPredictionsList(context, dummyConditions);
// Get conditions directly from ViewModel
final conditions = symptomsCheckerViewModel.currentConditions ?? [];
// Get selected symptoms names for display
final symptoms = symptomsCheckerViewModel
.getAllSelectedSymptoms()
.map((s) => s.commonName ?? s.name ?? '')
.where((name) => name.isNotEmpty)
.take(3)
.toList();
return _buildPredictionsList(context, conditions, symptoms);
},
),
),

@ -196,7 +196,7 @@ class _RiskFactorsScreenState extends State<RiskFactorsScreen> {
border: Border.all(color: AppColors.bottomNAVBorder, width: 1),
),
child: Text(
'Not Applicable Risk Factor',
'Not Applicable Risk Factor'.needTranslation,
style: TextStyle(fontSize: 14.f, color: AppColors.textColor),
),
).toShimmer2(isShow: true, radius: 24.r);

@ -4,12 +4,12 @@ import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -82,10 +82,12 @@ class _SymptomsSelectorPageState extends State<SymptomsSelectorPage> {
title: LocaleKeys.symptomsSelector.tr(),
leadingCallback: () => _buildConfirmationBottomSheet(
context: context,
onConfirm: () => {
context.pop(),
context.pop(),
}),
onConfirm: () {
context.pop();
viewModel.reset(); // Clear all symptoms checker data
final navigationService = getIt.get<NavigationService>();
navigationService.replaceAllRoutesAndNavigateToLanding();
}),
child: viewModel.isBodySymptomsLoading
? _buildLoadingShimmer()
: viewModel.organSymptomsResults.isEmpty

@ -14,6 +14,7 @@ import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_v
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/custom_progress_bar.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -119,12 +120,14 @@ class _TriagePageState extends State<TriagePage> {
SizedBox(height: 8.h),
LocaleKeys.emergencyTriage.tr(context: context).toText28(color: AppColors.whiteColor, isBold: true),
SizedBox(height: 8.h),
LocaleKeys.emergencyEvidenceDetected.tr(context: context)
.toText14(color: AppColors.whiteColor, weight: FontWeight.w500),
LocaleKeys.emergencyEvidenceDetected.tr(context: context).toText14(color: AppColors.whiteColor, weight: FontWeight.w500),
SizedBox(height: 24.h),
CustomButton(
text: LocaleKeys.confirm.tr(context: context),
onPressed: () async => Navigator.of(context).pop(),
onPressed: () {
context.pop();
context.navigateWithName(AppRoutes.possibleConditionsPage);
},
backgroundColor: AppColors.whiteColor,
borderColor: AppColors.whiteColor,
textColor: AppColors.primaryRedColor,
@ -197,7 +200,7 @@ class _TriagePageState extends State<TriagePage> {
// Call API with updated evidence
viewModel.getDiagnosisForTriage(
age: viewModel.selectedAge!,
sex: viewModel.selectedGender!.toLowerCase(),
gender: viewModel.selectedGender!.toLowerCase() == "male" ? 1 : 2,
evidenceIds: initialEvidenceIds,
triageEvidence: triageEvidence,
language: viewModel.appState.isArabic() ? 'ar' : 'en',
@ -307,8 +310,10 @@ class _TriagePageState extends State<TriagePage> {
isShowActionButtons: true,
onCancelTap: () => Navigator.pop(context),
onConfirmTap: () {
Navigator.pop(context);
context.pop();
viewModel.reset(); // Clear all symptoms checker data
final navigationService = getIt.get<NavigationService>();
navigationService.replaceAllRoutesAndNavigateToLanding();
},
),
callBackFunc: () {},

@ -16,14 +16,14 @@ import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:provider/provider.dart';
class UserInfoSelectionScreen extends StatefulWidget {
const UserInfoSelectionScreen({super.key});
class UserInfoSelectionPage extends StatefulWidget {
const UserInfoSelectionPage({super.key});
@override
State<UserInfoSelectionScreen> createState() => _UserInfoSelectionScreenState();
State<UserInfoSelectionPage> createState() => _UserInfoSelectionPageState();
}
class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
class _UserInfoSelectionPageState extends State<UserInfoSelectionPage> {
@override
void initState() {
super.initState();
@ -155,10 +155,12 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
// 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);
String heightText =
viewModel.selectedHeight != null ? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}" : LocaleKeys.notSet.tr(context: context);
String weightText =
viewModel.selectedWeight != null ? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}" : LocaleKeys.notSet.tr(context: context);
String heightText = viewModel.selectedHeight != null
? "${viewModel.selectedHeight!.round()} ${viewModel.isHeightCm ? 'cm' : 'ft'}"
: LocaleKeys.notSet.tr(context: context);
String weightText = viewModel.selectedWeight != null
? "${viewModel.selectedWeight!.round()} ${viewModel.isWeightKg ? 'kg' : 'lbs'}"
: LocaleKeys.notSet.tr(context: context);
return Column(
children: [
@ -176,9 +178,9 @@ class _UserInfoSelectionScreenState extends State<UserInfoSelectionScreen> {
child: Column(
children: [
LocaleKeys.helloIsYourInformationUpToDate.tr(namedArgs: {'name': name}).toText16(
weight: FontWeight.w600,
color: AppColors.textColor,
),
weight: FontWeight.w600,
color: AppColors.textColor,
),
SizedBox(height: 32.h),
_buildEditInfoTile(
context: context,

@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/core/app_export.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';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/triage_response_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/custom_progress_bar.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@ -12,33 +13,128 @@ import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
class ConditionCard extends StatelessWidget {
final IconData icon;
final String title;
final int percentage;
final String tagText;
final String clinic;
final TriageCondition condition;
final List<String> symptoms;
final String description;
final String? monitorNote;
final String? appointmentLabel;
final PossibleConditionsSeverityEnum possibleConditionsSeverityEnum;
final VoidCallback? onActionPressed;
const ConditionCard({
super.key,
required this.icon,
required this.title,
required this.percentage,
required this.tagText,
required this.clinic,
required this.condition,
required this.symptoms,
required this.description,
this.monitorNote,
this.appointmentLabel,
required this.possibleConditionsSeverityEnum,
this.onActionPressed,
});
/// Get title from condition
String get title => condition.commonName ?? condition.name ?? '';
/// Get percentage from probability (0-1 to 0-100)
int get percentage => ((condition.probability ?? 0) * 100).toInt();
/// Get clinic name from category
String get clinic => condition.conditionDetails?.category?.name ?? '';
/// Get description from hint
String get description => condition.conditionDetails?.hint ?? '';
/// Get severity enum based on triage level
PossibleConditionsSeverityEnum get severityEnum {
final triageLevel = condition.conditionDetails?.triageLevel;
if (triageLevel != null) {
switch (triageLevel) {
case TriageLevelEnum.emergencyAmbulance:
case TriageLevelEnum.emergency:
return PossibleConditionsSeverityEnum.emergency;
case TriageLevelEnum.consultation24:
case TriageLevelEnum.consultation:
return PossibleConditionsSeverityEnum.seekMedicalAdvice;
case TriageLevelEnum.selfCare:
return PossibleConditionsSeverityEnum.monitorOnly;
}
}
// Default based on probability
if (percentage >= 70) {
return PossibleConditionsSeverityEnum.seekMedicalAdvice;
} else {
return PossibleConditionsSeverityEnum.monitorOnly;
}
}
/// Get tag text based on triage level
String getTagText(BuildContext context) {
final triageLevel = condition.conditionDetails?.triageLevel;
if (triageLevel != null) {
switch (triageLevel) {
case TriageLevelEnum.emergencyAmbulance:
case TriageLevelEnum.emergency:
return LocaleKeys.emergency.tr(context: context);
case TriageLevelEnum.consultation24:
case TriageLevelEnum.consultation:
return "Seek Medical Advice".needTranslation;
case TriageLevelEnum.selfCare:
return LocaleKeys.monitor.tr(context: context);
}
}
// Default based on probability
if (percentage >= 70) {
return "Seek Medical Advice".needTranslation;
} else {
return LocaleKeys.monitor.tr(context: context);
}
}
/// Get monitor note if applicable
String? getMonitorNote(BuildContext context) {
if (severityEnum == PossibleConditionsSeverityEnum.monitorOnly) {
return "No need to visit doctor".needTranslation;
}
return null;
}
/// Get icon based on category name from API
IconData get icon {
final categoryName = condition.conditionDetails?.category?.name?.toLowerCase() ?? '';
if (categoryName.contains('cardio') || categoryName.contains('heart')) {
return Icons.favorite;
} else if (categoryName.contains('neuro') || categoryName.contains('brain')) {
return Icons.psychology_alt;
} else if (categoryName.contains('psychiatr') || categoryName.contains('mental')) {
return Icons.psychology;
} else if (categoryName.contains('eye') || categoryName.contains('ophthalmo')) {
return Icons.remove_red_eye;
} else if (categoryName.contains('ent') || categoryName.contains('ear')) {
return Icons.hearing;
} else if (categoryName.contains('derma') || categoryName.contains('skin')) {
return Icons.face;
} else if (categoryName.contains('ortho') || categoryName.contains('bone')) {
return Icons.accessibility_new;
} else if (categoryName.contains('gyneco') || categoryName.contains('obstet')) {
return Icons.pregnant_woman;
} else if (categoryName.contains('pediatr') || categoryName.contains('child')) {
return Icons.child_care;
} else if (categoryName.contains('surg')) {
return Icons.local_hospital;
} else if (categoryName.contains('pulmo') || categoryName.contains('lung') || categoryName.contains('respiratory')) {
return Icons.air;
} else if (categoryName.contains('gastro') || categoryName.contains('digest')) {
return Icons.restaurant;
} else if (categoryName.contains('nephro') || categoryName.contains('kidney')) {
return Icons.water_drop;
} else if (categoryName.contains('hemato') || categoryName.contains('blood')) {
return Icons.bloodtype;
} else if (categoryName.contains('infect') || categoryName.contains('virus')) {
return Icons.coronavirus;
} else if (categoryName.contains('emergency')) {
return Icons.emergency;
} else if (categoryName.contains('geriatr') || categoryName.contains('elder')) {
return Icons.elderly;
} else if (categoryName.contains('internal')) {
return Icons.medical_services;
}
return Icons.medical_services;
}
Color getChipColorBySeverityEnum(PossibleConditionsSeverityEnum possibleConditionsSeverityEnum) {
switch (possibleConditionsSeverityEnum) {
case PossibleConditionsSeverityEnum.seekMedicalAdvice:
@ -67,7 +163,7 @@ class ConditionCard extends StatelessWidget {
crossAxisAlignment: WrapCrossAlignment.center,
children: [
for (int i = 0; i < symptoms.length; i++) ...[
Text(symptoms[i], style: TextStyle(color: AppColors.greyTextColor, fontWeight: FontWeight.w500, fontSize: 12.f)),
"${symptoms[i]}".toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor),
if (i != symptoms.length - 1)
Padding(
padding: EdgeInsets.symmetric(horizontal: 2.w),
@ -80,9 +176,12 @@ class ConditionCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
// final monitorNote = getMonitorNote(context);
final tagText = getTagText(context);
return Container(
width: double.infinity,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -90,14 +189,14 @@ class ConditionCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 48.w,
height: 48.w,
width: 32.w,
height: 32.w,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: getChipColorBySeverityEnum(possibleConditionsSeverityEnum).withValues(alpha: 0.2),
borderRadius: 12.r,
color: getChipColorBySeverityEnum(severityEnum).withValues(alpha: 0.2),
borderRadius: 8.r,
hasShadow: false,
),
child: Icon(icon, color: getChipTextColorBySeverityEnum(possibleConditionsSeverityEnum), size: 24.f),
child: Icon(icon, color: getChipTextColorBySeverityEnum(severityEnum), size: 16.f),
),
SizedBox(width: 12.w),
Expanded(
@ -108,34 +207,23 @@ class ConditionCard extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
title,
style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, color: AppColors.textColor),
overflow: TextOverflow.ellipsis,
),
child: title.toText18(weight: FontWeight.w600, color: AppColors.textColor),
),
AppCustomChipWidget(
labelText: tagText,
backgroundColor: getChipColorBySeverityEnum(possibleConditionsSeverityEnum).withValues(alpha: 0.2),
textColor: getChipTextColorBySeverityEnum(possibleConditionsSeverityEnum),
backgroundColor: getChipColorBySeverityEnum(severityEnum).withValues(alpha: 0.2),
textColor: getChipTextColorBySeverityEnum(severityEnum),
),
],
),
CustomRoundedProgressBar(
percentage: percentage,
height: 6.h,
color: getChipColorBySeverityEnum(possibleConditionsSeverityEnum),
color: getChipColorBySeverityEnum(severityEnum),
backgroundColor: AppColors.scaffoldBgColor,
titleWidget: Row(
children: [
Text(
"$percentage%",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12.f,
color: getChipColorBySeverityEnum(possibleConditionsSeverityEnum),
),
),
"$percentage%".toText12(fontWeight: FontWeight.bold, color: getChipColorBySeverityEnum(severityEnum)),
],
).paddingSymmetrical(0, 4.h),
),
@ -148,50 +236,42 @@ class ConditionCard extends StatelessWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
clinic,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14.f,
color: AppColors.textColor,
),
),
"Common Symptom".needTranslation.toText14(weight: FontWeight.w600, color: AppColors.textColor),
_buildSymptomsRow(),
SizedBox(height: 16.h),
Text(LocaleKeys.description.tr(context: context), style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.f, color: AppColors.textColor)),
LocaleKeys.description.tr(context: context).toText14(weight: FontWeight.bold, color: AppColors.textColor),
SizedBox(height: 2.h),
Text(description, style: TextStyle(color: AppColors.greyTextColor, fontWeight: FontWeight.w500, fontSize: 12.f)),
if (possibleConditionsSeverityEnum == PossibleConditionsSeverityEnum.emergency)
description.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor),
if (severityEnum != PossibleConditionsSeverityEnum.monitorOnly)
CustomButton(
text: appointmentLabel ?? LocaleKeys.bookAppointment.tr(context: context),
text: "Book Appointment".needTranslation,
onPressed: () {
if (onActionPressed != null) {
onActionPressed!();
}
},
backgroundColor: AppColors.lightRedButtonColor,
backgroundColor: AppColors.primaryRedColor,
borderColor: Colors.transparent,
textColor: AppColors.primaryRedColor,
fontSize: 16.f,
textColor: AppColors.whiteColor,
fontSize: 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 48.h,
icon: AppAssets.add_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 18.h,
height: 48.h,
iconColor: AppColors.whiteColor,
).paddingOnly(top: 16.w),
if (monitorNote != null)
Container(
margin: EdgeInsets.only(top: 12.h),
child: AppCustomChipWidget(
labelText: monitorNote!,
backgroundColor: AppColors.whiteColor,
textColor: AppColors.textColor,
padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 8.h),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.r)),
),
),
// if (monitorNote != null)
// Container(
// margin: EdgeInsets.only(top: 12.h),
// child: AppCustomChipWidget(
// labelText: monitorNote,
// backgroundColor: AppColors.whiteColor,
// textColor: AppColors.textColor,
// padding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 8.h),
// shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.r)),
// ),
// ),
],
).paddingAll(16.w),
],

@ -8,6 +8,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointme
import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/comprehensive_checkup_page.dart';
import 'package:hmg_patient_app_new/presentation/covid19test/covid19_landing_page.dart';
import 'package:hmg_patient_app_new/presentation/e_referral/new_e_referral.dart';
import 'package:hmg_patient_app_new/presentation/emergency_services/emergency_services_page.dart';
import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts/health_calculators_page.dart';
import 'package:hmg_patient_app_new/presentation/health_trackers/add_health_tracker_entry_page.dart';
import 'package:hmg_patient_app_new/presentation/health_trackers/health_tracker_detail_page.dart';
@ -30,16 +31,14 @@ import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_page.dart
import 'package:hmg_patient_app_new/presentation/water_monitor/water_consumption_page.dart';
import 'package:hmg_patient_app_new/presentation/water_monitor/water_monitor_settings_page.dart';
import 'package:hmg_patient_app_new/splashPage.dart';
import '../features/qr_parking/qr_parking_view_model.dart';
import '../presentation/covid19test/covid19_landing_page.dart';
import 'package:provider/provider.dart';
import '../core/dependencies.dart';
import '../features/monthly_reports/monthly_reports_repo.dart';
import '../features/monthly_reports/monthly_reports_view_model.dart';
import '../features/qr_parking/qr_parking_view_model.dart';
import '../presentation/parking/paking_page.dart';
import '../services/error_handler_service.dart';
import 'package:provider/provider.dart';
class AppRoutes {
static const String initialRoute = '/initialRoute';
@ -84,8 +83,10 @@ class AppRoutes {
static const String addHealthTrackerEntryPage = '/addHealthTrackerEntryPage';
static const String healthTrackerDetailPage = '/healthTrackerDetailPage';
static Map<String, WidgetBuilder> get routes =>
{
// Emergency Services
static const String emergencyServicesPage = '/emergencyServicesPage';
static Map<String, WidgetBuilder> get routes => {
initialRoute: (context) => SplashPage(),
loginScreen: (context) => LoginScreen(),
landingScreen: (context) => LandingNavigation(),
@ -104,7 +105,7 @@ class AppRoutes {
triagePage: (context) => TriagePage(),
bloodDonationPage: (context) => BloodDonationPage(),
bookAppointmentPage: (context) => BookAppointmentPage(),
userInfoSelection: (context) => UserInfoSelectionScreen(),
userInfoSelection: (context) => UserInfoSelectionPage(),
userInfoFlowManager: (context) => UserInfoFlowManager(),
smartWatches: (context) => SmartwatchInstructionsPage(),
huaweiHealthExample: (context) => HuaweiHealthExample(),
@ -115,34 +116,28 @@ class AppRoutes {
healthConvertersPage: (context) => HealthCalculatorsPage(type: HealthCalConEnum.converter),
healthTrackersPage: (context) => HealthTrackersPage(),
vitalSign: (context) => VitalSignPage(),
emergencyServicesPage: (context) => EmergencyServicesPage(),
addHealthTrackerEntryPage: (context) {
final args = ModalRoute
.of(context)
?.settings
.arguments as HealthTrackerTypeEnum?;
final args = ModalRoute.of(context)?.settings.arguments as HealthTrackerTypeEnum?;
return AddHealthTrackerEntryPage(
trackerType: args ?? HealthTrackerTypeEnum.bloodSugar,
);
},
healthTrackerDetailPage: (context) {
final args = ModalRoute
.of(context)
?.settings
.arguments as HealthTrackerTypeEnum?;
final args = ModalRoute.of(context)?.settings.arguments as HealthTrackerTypeEnum?;
return HealthTrackerDetailPage(
trackerType: args ?? HealthTrackerTypeEnum.bloodSugar,
);
},
monthlyReports: (context) => ChangeNotifierProvider(
create: (_) => MonthlyReportsViewModel(
monthlyReportsRepo: getIt<MonthlyReportsRepo>(),
errorHandlerService: getIt<ErrorHandlerService>(),
),
),
qrParking: (context) => ChangeNotifierProvider<QrParkingViewModel>(
create: (_) => getIt<QrParkingViewModel>(),
child: const ParkingPage(),
monthlyReports: (context) => ChangeNotifierProvider(
create: (_) => MonthlyReportsViewModel(
monthlyReportsRepo: getIt<MonthlyReportsRepo>(),
errorHandlerService: getIt<ErrorHandlerService>(),
),
),
qrParking: (context) => ChangeNotifierProvider<QrParkingViewModel>(
create: (_) => getIt<QrParkingViewModel>(),
child: const ParkingPage(),
)
};
}

@ -92,7 +92,7 @@ class AppColors {
// SymptomsChecker
static const Color chipColorSeekMedicalAdvice = Color(0xFFFFAF15); // #FFAF15
static const Color chipTextColorSeekMedicalAdvice = Color(0xFFAB7103); // #AB7103
static const Color chipTextColorSeekMedicalAdvice = Color(0xFFD48D05); // #AB7103
static const Color chipColorMonitor = Color(0xFF18C273); // #18C273
static const Color chipColorEmergency = Color(0xFFED1C2B); // #ED1C2B

@ -80,17 +80,14 @@ class CustomButton extends StatelessWidget {
),
Visibility(
visible: text.isNotEmpty,
child: Padding(
padding: EdgeInsets.only(top: 0),
child: Text(
text,
overflow: textOverflow,
style: context.dynamicTextStyle(
fontSize: fontS,
color: isDisabled ? AppColors.greyTextColor : textColor,
letterSpacing: 0,
fontWeight: fontWeight,
),
child: Text(
text,
overflow: textOverflow,
style: context.dynamicTextStyle(
fontSize: fontS,
color: isDisabled ? AppColors.greyTextColor : textColor,
letterSpacing: 0,
fontWeight: fontWeight,
),
),
),

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save