diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index a7e2c44d..2fd29149 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -1490,6 +1490,7 @@ "explainWhy": "اشرح، لماذا؟", "whyThisCondition": "لماذا هذه الحالة؟", "supportingEvidence": "الأدلة الداعمة", + "explanationDescription": "بناءً على الأعراض التي أبلغت عنها، تم تحديد هذه الحالة كتطابق محتمل. الأعراض التالية تدعم هذا التقييم:", "symptomsCheckerFindingScore": "نسبة التطابق", "notSet": "غير محدد", "years": "سنوات", diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 77d0e46e..43ff1c11 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -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", diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 91fc3ef4..06d590cf 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -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().getLanguageCode() == "ar" ? 'CairoArabic' : 'Poppins', ), diff --git a/lib/features/symptoms_checker/models/resp_models/triage_response_model.dart b/lib/features/symptoms_checker/models/resp_models/triage_response_model.dart index ea4d823b..60b748b1 100644 --- a/lib/features/symptoms_checker/models/resp_models/triage_response_model.dart +++ b/lib/features/symptoms_checker/models/resp_models/triage_response_model.dart @@ -104,11 +104,21 @@ class TriageQuestion { }); factory TriageQuestion.fromJson(Map 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.from(json['instruction']) : null, + explication: json['explication'] ?? dummyExplication, // Use dummy if null + instruction: json['instruction'] != null ? List.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 { diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 035596a2..51e27211 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -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'; diff --git a/lib/presentation/symptoms_checker/possible_conditions_screen.dart b/lib/presentation/symptoms_checker/possible_conditions_screen.dart index 59a03580..b8807b83 100644 --- a/lib/presentation/symptoms_checker/possible_conditions_screen.dart +++ b/lib/presentation/symptoms_checker/possible_conditions_screen.dart @@ -55,8 +55,9 @@ class _PossibleConditionsPageState extends State { // 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 { 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, diff --git a/lib/presentation/symptoms_checker/triage_screen.dart b/lib/presentation/symptoms_checker/triage_screen.dart index 93a4561f..968bae3a 100644 --- a/lib/presentation/symptoms_checker/triage_screen.dart +++ b/lib/presentation/symptoms_checker/triage_screen.dart @@ -337,12 +337,14 @@ class _TriagePageState extends State { ), 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 { 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 { ), callBackFunc: () {}, isFullScreen: false, - isCloseButtonVisible: false, + isCloseButtonVisible: true, ); } @@ -569,17 +573,11 @@ class _TriagePageState extends State { 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 { 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, + ), ) ], ), diff --git a/lib/presentation/symptoms_checker/widgets/condition_card.dart b/lib/presentation/symptoms_checker/widgets/condition_card.dart index b6d97fa4..607b49da 100644 --- a/lib/presentation/symptoms_checker/widgets/condition_card.dart +++ b/lib/presentation/symptoms_checker/widgets/condition_card.dart @@ -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) { diff --git a/lib/presentation/symptoms_checker/widgets/triage_level_card.dart b/lib/presentation/symptoms_checker/widgets/triage_level_card.dart index 7fcb93c4..19ad3935 100644 --- a/lib/presentation/symptoms_checker/widgets/triage_level_card.dart +++ b/lib/presentation/symptoms_checker/widgets/triage_level_card.dart @@ -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 createState() => _TriageLevelCardState(); +} + +class _TriageLevelCardState extends State with SingleTickerProviderStateMixin { + late AnimationController _animationController; + late Animation _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(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: [