completed the symmtoms checker suggestions from infermedica

pull/327/head
faizatflutter 3 days ago
parent 02e44ed7fb
commit ce23c0302a

@ -1490,6 +1490,7 @@
"explainWhy": "اشرح، لماذا؟",
"whyThisCondition": "لماذا هذه الحالة؟",
"supportingEvidence": "الأدلة الداعمة",
"explanationDescription": "بناءً على الأعراض التي أبلغت عنها، تم تحديد هذه الحالة كتطابق محتمل. الأعراض التالية تدعم هذا التقييم:",
"symptomsCheckerFindingScore": "نسبة التطابق",
"notSet": "غير محدد",
"years": "سنوات",

@ -1482,6 +1482,7 @@
"explainWhy": "Explain, Why?",
"whyThisCondition": "Why this condition?",
"supportingEvidence": "Supporting Evidence",
"explanationDescription": "Based on your reported symptoms, this condition has been identified as a possible match. The following symptoms support this assessment:",
"symptomsCheckerFindingScore": "- Symptoms checker finding score",
"notSet": "Not set",
"years": "Years",

@ -127,6 +127,7 @@ extension EmailValidator on String {
Widget toText12(
{bool isEnglishOnly = false,
Color? color,
Color? decorationColor,
bool isUnderLine = false,
TextAlign textAlignment = TextAlign.start,
bool isBold = false,
@ -147,7 +148,7 @@ extension EmailValidator on String {
color: color ?? AppColors.blackColor,
letterSpacing: letterSpacing ?? 0,
height: height,
decorationColor: isUnderLine ? AppColors.blackColor : null,
decorationColor: decorationColor ?? AppColors.blackColor,
decoration: isUnderLine ? TextDecoration.underline : null,
fontFamily: getIt.get<AppState>().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins',
),

@ -104,11 +104,21 @@ class TriageQuestion {
});
factory TriageQuestion.fromJson(Map<String, dynamic> json) {
// TESTING: Add dummy instructions to every question for testing UI
final dummyInstructions = [
"Clean the thermometer using soap and cold water.",
"For rectal insertion, apply a small amount of petroleum jelly to the end of the thermometer.",
"Insert the silver tip into the anus or mouth.",
"Hold the thermometer in place for 2 minutes, or as long as recommended by the manufacturer.",
];
final dummyExplication = "The most accurate measurement is rectal temperature. You can also measure the temperature under the armpit or use contactless thermometers. However, using the contact thermometer is more accurate. Follow the thermometer manufacturer's instructions. A temperature above 37°C or 98.6°F is classified as a fever.";
return TriageQuestion(
type: json['type'],
text: json['text'],
explication: json['explication'],
instruction: json['instruction'] != null ? List<String>.from(json['instruction']) : null,
explication: json['explication'] ?? dummyExplication, // Use dummy if null
instruction: json['instruction'] != null ? List<String>.from(json['instruction']) : dummyInstructions, // Use dummy if null
items: json['items'] != null ? (json['items'] as List).map((item) => TriageQuestionItem.fromJson(item)).toList() : null,
);
}
@ -123,8 +133,12 @@ class TriageQuestion {
};
}
/// Check if instruction is available
bool get hasInstructions => instruction != null && instruction!.isNotEmpty;
/// Check if instruction OR explication is available
bool get hasInstructions {
final hasInstruction = instruction != null && instruction!.isNotEmpty;
final hasExplication = explication != null && explication!.isNotEmpty;
return hasInstruction || hasExplication;
}
}
class TriageQuestionItem {

@ -1485,6 +1485,7 @@ abstract class LocaleKeys {
static const explainWhy = 'explainWhy';
static const whyThisCondition = 'whyThisCondition';
static const supportingEvidence = 'supportingEvidence';
static const explanationDescription = 'explanationDescription';
static const symptomsCheckerFindingScore = 'symptomsCheckerFindingScore';
static const notSet = 'notSet';
static const years = 'years';

@ -55,8 +55,9 @@ class _PossibleConditionsPageState extends State<PossibleConditionsPage> {
// Call GetTriage API to fetch triage level
_fetchTriageLevel();
// Clear all selections (organs, symptoms, risk factors, suggestions, questions) once user reaches possible conditions screen
symptomsCheckerViewModel.clearSelectionsForPossibleConditions();
// NOTE: Do NOT clear selections here - we need the evidence for getExplanations API
// The evidence (symptoms, risk factors, suggestions) is needed when user taps "Explain, Why?" on condition cards
// symptomsCheckerViewModel.clearSelectionsForPossibleConditions();
});
}
@ -203,7 +204,11 @@ class _PossibleConditionsPageState extends State<PossibleConditionsPage> {
loadingText: LocaleKeys.areYouSureYouWantToExitProgress.tr(context: context),
isShowActionButtons: true,
onCancelTap: () => Navigator.pop(context),
onConfirmTap: () => onConfirm(),
onConfirmTap: () {
// Clear all evidence before exiting
symptomsCheckerViewModel.clearSelectionsForPossibleConditions();
onConfirm();
},
),
callBackFunc: () {},
isFullScreen: false,

@ -337,12 +337,14 @@ class _TriagePageState extends State<TriagePage> {
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: false,
isCloseButtonVisible: true,
);
}
void _showInstructionsBottomSheet(BuildContext context, TriageQuestion question) {
final instructions = question.instruction ?? [];
final hasExplication = question.explication != null && question.explication!.isNotEmpty;
final hasInstructions = instructions.isNotEmpty;
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.howTo.tr(context: context),
@ -351,40 +353,42 @@ class _TriagePageState extends State<TriagePage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Explication (if available)
if (question.explication != null && question.explication!.isNotEmpty) ...[
if (hasExplication) ...[
(question.explication!).toText14(
color: AppColors.textColor,
),
SizedBox(height: 16.h),
if (hasInstructions) SizedBox(height: 16.h),
],
// Instructions list with bullets
...instructions.asMap().entries.map((entry) {
final instruction = entry.value;
// Instructions list with bullets (if available)
if (hasInstructions) ...[
...instructions.asMap().entries.map((entry) {
final instruction = entry.value;
return Padding(
padding: EdgeInsets.only(bottom: 12.h),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.only(top: 6.h, right: 12.w, left: 12.w),
width: 6.w,
height: 6.h,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColors.primaryRedColor,
return Padding(
padding: EdgeInsets.only(bottom: 12.h),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: EdgeInsets.only(top: 6.h, right: 12.w, left: 12.w),
width: 6.w,
height: 6.h,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColors.primaryRedColor,
),
),
),
Expanded(
child: instruction.toText14(
color: AppColors.textColor,
Expanded(
child: instruction.toText14(
color: AppColors.textColor,
),
),
),
],
),
);
}).toList(),
],
),
);
}).toList(),
],
SizedBox(height: 24.h),
CustomButton(
@ -398,7 +402,7 @@ class _TriagePageState extends State<TriagePage> {
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: false,
isCloseButtonVisible: true,
);
}
@ -569,17 +573,11 @@ class _TriagePageState extends State<TriagePage> {
SizedBox(width: 8.w),
GestureDetector(
onTap: () => _showInstructionsBottomSheet(context, question),
child: Container(
padding: EdgeInsets.all(6.w),
decoration: BoxDecoration(
color: AppColors.primaryRedColor.withValues(alpha: 0.1),
shape: BoxShape.circle,
),
child: Icon(
Icons.help_outline,
size: 20.f,
color: AppColors.primaryRedColor,
),
child: Utils.buildSvgWithAssets(
icon: AppAssets.alertSquare,
height: 24.h,
width: 24.h,
iconColor: AppColors.textColor,
),
),
],
@ -662,17 +660,13 @@ class _TriagePageState extends State<TriagePage> {
onTap: () => _showRationaleBottomSheet(context),
child: Row(
children: [
Utils.buildSvgWithAssets(
icon: AppAssets.alertSquare,
height: 24.h,
width: 24.h,
iconColor: AppColors.textColor,
),
SizedBox(width: 12.w),
Expanded(
child: LocaleKeys.whyAmIBeingAskedThis
.tr(context: context)
.toText12(color: AppColors.greyInfoTextColor, fontWeight: FontWeight.w600),
child: LocaleKeys.whyAmIBeingAskedThis.tr(context: context).toText12(
decorationColor: AppColors.primaryRedColor,
isUnderLine: true,
color: AppColors.primaryRedColor,
fontWeight: FontWeight.w600,
),
)
],
),

@ -255,9 +255,11 @@ class ConditionCard extends StatelessWidget {
SizedBox(height: 8.h),
GestureDetector(
onTap: () => _showExplanationBottomSheet(context),
child: LocaleKeys.explainWhy.tr(context: context).toText14(
child: LocaleKeys.explainWhy.tr(context: context).toText12(
isBold: true,
color: AppColors.primaryRedColor,
isUnderLine: true,
decorationColor: AppColors.primaryRedColor,
),
),
if (severityEnum != PossibleConditionsSeverityEnum.monitorOnly)
@ -309,8 +311,17 @@ class ConditionCard extends StatelessWidget {
return;
}
// Get all evidence
final evidence = viewModel.getAllEvidenceWithSources();
// Get all evidence with proper sources
final allEvidenceWithSources = viewModel.getAllEvidenceWithSources();
// Convert to format expected by GetExplanations API (with choice_id)
final evidence = allEvidenceWithSources.map((item) {
return {
"id": item["id"] ?? "",
"choice_id": "present",
"source": item["source"] ?? "initial",
};
}).toList();
// Show loading
LoaderBottomSheet.showLoader();
@ -331,9 +342,8 @@ class ConditionCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Supporting Evidence Section
LocaleKeys.supportingEvidence.tr(context: context).toText16(
isBold: true,
// Human-understandable description
LocaleKeys.explanationDescription.tr(context: context).toText14(
color: AppColors.textColor,
),
SizedBox(height: 12.h),
@ -356,7 +366,7 @@ class ConditionCard extends StatelessWidget {
height: 6.h,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColors.primaryRedColor,
color: AppColors.textColor,
),
),
Expanded(
@ -381,7 +391,7 @@ class ConditionCard extends StatelessWidget {
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: false,
isCloseButtonVisible: true,
);
},
onError: (error) {

@ -2,19 +2,54 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/extensions/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_level_response_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
class TriageLevelCard extends StatelessWidget {
class TriageLevelCard extends StatefulWidget {
final TriageLevelDataDetails triageLevelData;
const TriageLevelCard({super.key, required this.triageLevelData});
@override
State<TriageLevelCard> createState() => _TriageLevelCardState();
}
class _TriageLevelCardState extends State<TriageLevelCard> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _borderAnimation;
@override
void initState() {
super.initState();
// Create animation controller for border pulse effect
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 2000),
);
// Create animation that goes from 1.0 to 0.3 and back (for opacity)
_borderAnimation = Tween<double>(begin: 1.0, end: 0.3).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
),
);
// Start the animation and repeat
_animationController.repeat(reverse: true);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
/// Get localized title based on triage level
String getTitle(BuildContext context) {
final triageLevel = triageLevelData.triageLevel ?? '';
final triageLevel = widget.triageLevelData.triageLevel ?? '';
switch (triageLevel) {
case 'emergency_ambulance':
@ -34,7 +69,7 @@ class TriageLevelCard extends StatelessWidget {
/// Get localized description based on triage level
String getDescription(BuildContext context) {
final triageLevel = triageLevelData.triageLevel ?? '';
final triageLevel = widget.triageLevelData.triageLevel ?? '';
switch (triageLevel) {
case 'emergency_ambulance':
@ -54,7 +89,7 @@ class TriageLevelCard extends StatelessWidget {
/// Get color based on triage level
Color getBackgroundColor() {
final triageLevel = triageLevelData.triageLevel ?? '';
final triageLevel = widget.triageLevelData.triageLevel ?? '';
switch (triageLevel) {
case 'emergency_ambulance':
@ -72,7 +107,7 @@ class TriageLevelCard extends StatelessWidget {
/// Get border color based on triage level
Color getBorderColor() {
final triageLevel = triageLevelData.triageLevel ?? '';
final triageLevel = widget.triageLevelData.triageLevel ?? '';
switch (triageLevel) {
case 'emergency_ambulance':
@ -90,7 +125,7 @@ class TriageLevelCard extends StatelessWidget {
/// Get icon based on triage level
IconData getIcon() {
final triageLevel = triageLevelData.triageLevel ?? '';
final triageLevel = widget.triageLevelData.triageLevel ?? '';
switch (triageLevel) {
case 'emergency_ambulance':
@ -110,7 +145,7 @@ class TriageLevelCard extends StatelessWidget {
/// Get root cause text (localized)
String? getRootCauseText(BuildContext context) {
final rootCause = triageLevelData.rootCause ?? '';
final rootCause = widget.triageLevelData.rootCause ?? '';
switch (rootCause) {
case 'emergency_evidence_present':
@ -138,13 +173,33 @@ class TriageLevelCard extends StatelessWidget {
final title = getTitle(context);
final description = getDescription(context);
final rootCauseText = getRootCauseText(context);
final seriousSymptoms = triageLevelData.serious ?? [];
final seriousSymptoms = widget.triageLevelData.serious ?? [];
return Container(
width: double.infinity,
margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 8.h),
padding: EdgeInsets.all(20.w),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
return AnimatedBuilder(
animation: _borderAnimation,
builder: (context, child) {
return Container(
width: double.infinity,
margin: EdgeInsets.only(left: 24.w, right: 24.w, top: 16.h, bottom: 8.h),
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(24.r),
border: Border.all(
color: borderColor.withValues(alpha: _borderAnimation.value),
width: 2.w,
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.08),
blurRadius: 10.r,
offset: Offset(0, 2.h),
),
],
),
child: child,
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [

Loading…
Cancel
Save