import 'dart:developer'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/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/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/custom_progress_bar.dart'; import 'package:hmg_patient_app_new/services/dialog_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'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:provider/provider.dart'; class TriageScreen extends StatefulWidget { const TriageScreen({super.key}); @override State createState() => _TriageScreenState(); } class _TriageScreenState extends State { List answeredEvidenceIds = []; // Track user's answers late SymptomsCheckerViewModel viewModel; late DialogService dialogService; @override void initState() { super.initState(); viewModel = context.read(); dialogService = getIt.get(); // Start triage process when screen loads WidgetsBinding.instance.addPostFrameCallback((_) { _startTriage(); }); } void _startTriage() { viewModel.startOrContinueTriage( onSuccess: () { _handleTriageResponse(); }, onError: (error) { dialogService.showErrorBottomSheet( message: error, onOkPressed: () => context.pop(), ); }, ); } void _handleTriageResponse() { if (viewModel.hasEmergencyEvidence) { _showEmergencyDialog(); return; } if (viewModel.shouldStopTriage) { // Navigate to results/possible conditions screen context.navigateWithName(AppRoutes.possibleConditionsScreen); return; } // Question is loaded, reset selection for new question viewModel.resetTriageChoice(); } void _showEmergencyDialog() { showCommonBottomSheetWithoutHeight( context, title: "Emergency".needTranslation, child: Utils.getWarningWidget( loadingText: "Emergency evidence detected. Please seek immediate medical attention.".needTranslation, isShowActionButtons: true, onCancelTap: () => Navigator.pop(context), onConfirmTap: () { Navigator.pop(context); context.pop(); }, ), isFullScreen: false, isCloseButtonVisible: true, ); } bool get isFirstQuestion => answeredEvidenceIds.isEmpty; void _onOptionSelected(int choiceIndex) { viewModel.selectTriageChoice(choiceIndex); } void _onPreviousPressed() { context.pop(); } void _onNextPressed() { // Check if user has selected an option if (viewModel.selectedTriageChoiceIndex == null) { dialogService.showErrorBottomSheet(message: 'Please select an option before proceeding'.needTranslation); return; } // Get the selected choice from the current question final currentQuestion = viewModel.currentTriageQuestion; if (currentQuestion?.items == null || currentQuestion!.items!.isEmpty) { dialogService.showErrorBottomSheet( message: 'No question items available'.needTranslation, ); return; } final questionItem = currentQuestion.items!.first; if (questionItem.choices == null || viewModel.selectedTriageChoiceIndex! >= questionItem.choices!.length) { dialogService.showErrorBottomSheet( message: 'Invalid choice selection'.needTranslation, ); return; } final selectedChoice = questionItem.choices![viewModel.selectedTriageChoiceIndex!]; final evidenceId = selectedChoice.label ?? ""; if (evidenceId.isNotEmpty) { answeredEvidenceIds.add(evidenceId); } // Get all previous evidence IDs List allEvidenceIds = viewModel.getAllEvidenceIds(); allEvidenceIds.addAll(answeredEvidenceIds); log("allEvidences: ${allEvidenceIds.toString()}"); // Call API with updated evidence viewModel.getDiagnosisForTriage( age: viewModel.selectedAge!, sex: viewModel.selectedGender!.toLowerCase(), evidenceIds: allEvidenceIds, language: viewModel.appState.isArabic() ? 'ar' : 'en', onSuccess: (response) { _handleTriageResponse(); }, onError: (error) { dialogService.showErrorBottomSheet(message: error); }, ); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Consumer( builder: (context, viewModel, child) { // Show normal question UI return Column( children: [ Expanded( child: CollapsingListView( title: "Triage".needTranslation, leadingCallback: () => _showConfirmationBeforeExit(context), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), _buildQuestionCard(viewModel), ], ), ), ), _buildStickyBottomCard(context, viewModel), ], ); }, ), ); } Widget _buildLoadingShimmer() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), // Create 2-3 shimmer cards ...List.generate(1, (index) { return Padding( padding: EdgeInsets.only(bottom: 16.h), child: _buildShimmerCard(), ); }), ], ); } Widget _buildShimmerCard() { return Container( width: double.infinity, margin: EdgeInsets.symmetric(horizontal: 24.w), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 16.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Shimmer title Container( height: 40.h, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(24.r), ), ).toShimmer2(isShow: true, radius: 24.r), SizedBox(height: 16.h), // Shimmer chips Wrap( runSpacing: 12.h, spacing: 8.w, children: List.generate(4, (index) { return Container( padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h), decoration: BoxDecoration( color: AppColors.whiteColor, borderRadius: BorderRadius.circular(24.r), border: Border.all(color: AppColors.bottomNAVBorder, width: 1), ), child: Text( 'Not Applicable Suggestion', style: TextStyle(fontSize: 14.f, color: AppColors.textColor), ), ).toShimmer2(isShow: true, radius: 24.r); }), ), ], ), ); } Widget _buildErrorState() { return CollapsingListView( title: "Triage".needTranslation, leadingCallback: () => context.pop(), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.error_outline, size: 64.f, color: AppColors.errorColor), SizedBox(height: 16.h), "No question available".needTranslation.toText16(weight: FontWeight.w500), SizedBox(height: 24.h), CustomButton( text: "Go Back".needTranslation, onPressed: () => context.pop(), backgroundColor: AppColors.primaryRedColor, ).paddingSymmetrical(48.w, 0), ], ), ), ); } void _showConfirmationBeforeExit(BuildContext context) { showCommonBottomSheetWithoutHeight( title: LocaleKeys.notice.tr(context: context), context, child: Utils.getWarningWidget( loadingText: "Are you sure you want to exit? Your progress will be lost.".needTranslation, isShowActionButtons: true, onCancelTap: () => Navigator.pop(context), onConfirmTap: () { Navigator.pop(context); context.pop(); }, ), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); } Widget _buildQuestionCard(SymptomsCheckerViewModel viewModel) { if (viewModel.isTriageDiagnosisLoading) { return _buildLoadingShimmer(); } if (viewModel.currentTriageQuestion == null) { return Center( child: "No question available".needTranslation.toText16(weight: FontWeight.w500), ); } final question = viewModel.currentTriageQuestion; if (question == null || question.items == null || question.items!.isEmpty) { return SizedBox.shrink(); } final questionItem = question.items!.first; final choices = questionItem.choices ?? []; return AnimatedSwitcher( duration: const Duration(milliseconds: 400), transitionBuilder: (Widget child, Animation animation) { final offsetAnimation = Tween( begin: const Offset(1.0, 0.0), end: Offset.zero, ).animate( CurvedAnimation( parent: animation, curve: Curves.easeInOut, ), ); return SlideTransition( position: offsetAnimation, child: FadeTransition( opacity: animation, child: child, ), ); }, child: Container( key: ValueKey(questionItem.id ?? answeredEvidenceIds.length.toString()), width: double.infinity, margin: EdgeInsets.symmetric(horizontal: 24.w), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), padding: EdgeInsets.symmetric(vertical: 24.h, horizontal: 20.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ (question.text ?? "").toText16(weight: FontWeight.w500), SizedBox(height: 24.h), ...List.generate(choices.length, (index) { bool selected = viewModel.selectedTriageChoiceIndex == index; return _buildOptionItem(index, selected, choices[index].label ?? ""); }), ], ), ), ); } Widget _buildOptionItem(int index, bool selected, String optionText) { return GestureDetector( onTap: () => _onOptionSelected(index), child: Container( margin: EdgeInsets.only(bottom: 12.h), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ AnimatedContainer( duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, width: 24.w, height: 24.w, decoration: BoxDecoration( color: selected ? AppColors.primaryRedColor : Colors.transparent, borderRadius: BorderRadius.circular(5.r), border: Border.all(color: selected ? AppColors.primaryRedColor : AppColors.checkBoxBorderColor, width: 1.w), ), child: selected ? Icon(Icons.check, size: 16.f, color: AppColors.whiteColor) : null, ), SizedBox(width: 12.w), Expanded(child: optionText.toText14(weight: FontWeight.w500)), ], ), ), ); } Widget _buildStickyBottomCard(BuildContext context, SymptomsCheckerViewModel viewModel) { // Get the top condition with highest probability final conditions = viewModel.currentConditions ?? []; String suggestedCondition = "Analyzing..."; double probability = 0.0; if (conditions.isNotEmpty) { // Sort by probability descending final sortedConditions = List.from(conditions); sortedConditions.sort((a, b) => (b.probability ?? 0.0).compareTo(a.probability ?? 0.0)); final topCondition = sortedConditions.first; suggestedCondition = topCondition.commonName ?? topCondition.name ?? "Unknown"; probability = (topCondition.probability ?? 0.0) * 100; // Convert to percentage } return Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), child: Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox(height: 16.h), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ RichText( text: TextSpan( text: "Possible symptom: ".needTranslation, style: TextStyle( color: AppColors.greyTextColor, fontWeight: FontWeight.w600, fontSize: 14.f, ), children: [ TextSpan( text: suggestedCondition, style: TextStyle( color: AppColors.textColor, fontWeight: FontWeight.w600, fontSize: 14.f, ), ), ], ), ), SizedBox(height: 16.h), CustomRoundedProgressBar( percentage: probability.toInt(), paddingBetween: 5.h, color: AppColors.primaryRedColor, backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.17), height: 8.h, titleWidget: RichText( text: TextSpan( text: "${probability.toStringAsFixed(1)}% ", style: TextStyle( color: AppColors.primaryRedColor, fontWeight: FontWeight.w600, fontSize: 14.f, ), children: [ TextSpan( text: "- Symptoms checker finding score".needTranslation, style: TextStyle( color: AppColors.textColor, fontWeight: FontWeight.w500, fontSize: 13.f, ), ), ], ), ), ), SizedBox(height: 12.h), Row( children: [ Expanded( child: CustomButton( text: "Previous".needTranslation, onPressed: isFirstQuestion ? () {} : _onPreviousPressed, isDisabled: isFirstQuestion || viewModel.isTriageDiagnosisLoading, backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.11), borderColor: Colors.transparent, textColor: AppColors.primaryRedColor, fontSize: 16.f, ), ), SizedBox(width: 12.w), Expanded( child: CustomButton( text: "Next".needTranslation, isDisabled: viewModel.isTriageDiagnosisLoading, onPressed: _onNextPressed, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, fontSize: 16.f, ), ), ], ), ], ), SizedBox(height: 24.h), ], ).paddingSymmetrical(24.w, 0), ); } }