diff --git a/contexts/AuthContext.tsx b/contexts/AuthContext.tsx deleted file mode 100644 index 48571a8..0000000 --- a/contexts/AuthContext.tsx +++ /dev/null @@ -1,31 +0,0 @@ -// ...existing imports... -import { apiService } from '../services/api'; - -// ...existing code... - -export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - // ...existing state... - - const login = async (nationalId: string, password: string) => { - try { - setIsLoading(true); - - // Call the real API - const user = await apiService.getUserDetails(nationalId, password); - - // Store user data - await AsyncStorage.setItem('user', JSON.stringify(user)); - await AsyncStorage.setItem('authToken', user.authToken); - - setUser(user); - } catch (error) { - console.error('Login error:', error); - throw new Error('Invalid credentials or network error'); - } finally { - setIsLoading(false); - } - }; - - // ...existing code... -}; - diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 039787b..f366329 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -210,7 +210,7 @@ class ApiClientImp implements ApiClient { final int statusCode = response.statusCode; log("uri: ${Uri.parse(url.trim())}"); log("body: ${json.encode(body)}"); - log("response.body: ${response.body}"); + // log("response.body: ${response.body}"); if (statusCode < 200 || statusCode >= 400) { onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data")); logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); diff --git a/lib/features/symptoms_checker/symptoms_checker_repo.dart b/lib/features/symptoms_checker/symptoms_checker_repo.dart index 2d9a99b..c55c2f0 100644 --- a/lib/features/symptoms_checker/symptoms_checker_repo.dart +++ b/lib/features/symptoms_checker/symptoms_checker_repo.dart @@ -12,18 +12,21 @@ 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>> getUserDetails({ + Future>> + getUserDetails({ required String userName, required String password, }); - Future>> getBodySymptomsByName({ + Future>> + getBodySymptomsByName({ required List organNames, required String userSessionToken, required int gender, }); - Future>> getRiskFactors({ + Future>> + getRiskFactors({ required int age, required String sex, required List evidenceIds, @@ -33,7 +36,8 @@ abstract class SymptomsCheckerRepo { required String sessionId, }); - Future>> getSuggestions({ + Future>> + getSuggestions({ required int age, required String sex, required List evidenceIds, @@ -43,10 +47,12 @@ abstract class SymptomsCheckerRepo { required int gender, }); - Future>> getDiagnosisForTriage({ + Future>> + getDiagnosisForTriage({ required int age, required String sex, required List evidenceIds, + List>? triageEvidence, required String language, required String userSessionToken, required int gender, @@ -58,10 +64,12 @@ 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>> getUserDetails({ + Future>> + getUserDetails({ required String userName, required String password, }) async { @@ -84,9 +92,11 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { // Parse response if it's a string - final Map responseData = response is String ? jsonDecode(response) : response; + final Map responseData = + response is String ? jsonDecode(response) : response; - SymptomsUserDetailsResponseModel symptomsUserDetailsResponseModel = SymptomsUserDetailsResponseModel.fromJson(responseData); + SymptomsUserDetailsResponseModel symptomsUserDetailsResponseModel = + SymptomsUserDetailsResponseModel.fromJson(responseData); apiResponse = GenericApiModel( messageStatus: messageStatus ?? 1, @@ -113,7 +123,8 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { } @override - Future>> getBodySymptomsByName({ + Future>> + getBodySymptomsByName({ required List organNames, required String userSessionToken, required int gender, @@ -143,7 +154,8 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { - BodySymptomResponseModel bodySymptomResponse = BodySymptomResponseModel.fromJson(response); + BodySymptomResponseModel bodySymptomResponse = + BodySymptomResponseModel.fromJson(response); apiResponse = GenericApiModel( messageStatus: messageStatus ?? 1, @@ -152,7 +164,8 @@ 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()); } @@ -170,7 +183,8 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { } @override - Future>> getRiskFactors({ + Future>> + getRiskFactors({ required int age, required String sex, required List evidenceIds, @@ -211,9 +225,11 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { // Parse response if it's a string - final Map responseData = response is String ? jsonDecode(response) : response; + final Map responseData = + response is String ? jsonDecode(response) : response; - RiskAndSuggestionsResponseModel riskFactorsResponse = RiskAndSuggestionsResponseModel.fromJson(responseData); + RiskAndSuggestionsResponseModel riskFactorsResponse = + RiskAndSuggestionsResponseModel.fromJson(responseData); apiResponse = GenericApiModel( messageStatus: messageStatus ?? 1, @@ -239,22 +255,37 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { } } - @override - Future>> getDiagnosisForTriage({ + Future>> + getDiagnosisForTriage({ required int age, required String sex, required List evidenceIds, + List>? + 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 + List> evidenceList = []; + + // Add initial evidence as simple IDs + for (var id in evidenceIds) { + evidenceList.add({"id": id}); + } + + // Add triage evidence with proper format (id, choice_id, source) + if (triageEvidence != null && triageEvidence.isNotEmpty) { + evidenceList.addAll(triageEvidence); + } + final Map body = { "age": { "value": age, }, "sex": sex, - "evidence": evidenceIds.map((id) => {"id": id}).toList(), + "evidence": evidenceList, "language": language, "suggest_method": "diagnosis", "generalId": sessionId, @@ -282,11 +313,13 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { // Parse response if it's a string - final Map responseData = response is String ? jsonDecode(response) : response; + final Map responseData = + response is String ? jsonDecode(response) : response; final updatedResponseData = responseData['dataDetails']; - TriageDataDetails riskFactorsResponse = TriageDataDetails.fromJson(updatedResponseData); + TriageDataDetails riskFactorsResponse = + TriageDataDetails.fromJson(updatedResponseData); apiResponse = GenericApiModel( messageStatus: messageStatus ?? 1, @@ -295,7 +328,8 @@ 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()); } @@ -313,7 +347,8 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { } @override - Future>> getSuggestions({ + Future>> + getSuggestions({ required int age, required String sex, required List evidenceIds, @@ -354,9 +389,11 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { // Parse response if it's a string - final Map responseData = response is String ? jsonDecode(response) : response; + final Map responseData = + response is String ? jsonDecode(response) : response; - RiskAndSuggestionsResponseModel riskFactorsResponse = RiskAndSuggestionsResponseModel.fromJson(responseData); + RiskAndSuggestionsResponseModel riskFactorsResponse = + RiskAndSuggestionsResponseModel.fromJson(responseData); apiResponse = GenericApiModel( messageStatus: messageStatus ?? 1, diff --git a/lib/features/symptoms_checker/symptoms_checker_view_model.dart b/lib/features/symptoms_checker/symptoms_checker_view_model.dart index b41bab2..73768fe 100644 --- a/lib/features/symptoms_checker/symptoms_checker_view_model.dart +++ b/lib/features/symptoms_checker/symptoms_checker_view_model.dart @@ -47,7 +47,13 @@ class SymptomsCheckerViewModel extends ChangeNotifier { TriageDataDetails? triageDataDetails; // Triage state - int? _selectedTriageChoiceIndex; + int? + _selectedTriageChoiceIndex; // Deprecated - keeping for backward compatibility + final Map _selectedTriageChoicesByItemId = + {}; // Map of itemId -> choiceIndex for multi-item questions + final List> _triageEvidenceList = + []; // Store triage evidence with proper format + int _triageQuestionCount = 0; // Track number of triage questions answered // Selected risk factors tracking final Set _selectedRiskFactorIds = {}; @@ -60,7 +66,8 @@ 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; @@ -100,14 +107,17 @@ 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; @@ -117,8 +127,34 @@ class SymptomsCheckerViewModel extends ChangeNotifier { int? get selectedTriageChoiceIndex => _selectedTriageChoiceIndex; + /// Get the number of triage questions answered + int get triageQuestionCount => _triageQuestionCount; + + /// Get choice index for a specific item + int? getTriageChoiceForItem(String itemId) { + return _selectedTriageChoicesByItemId[itemId]; + } + + /// Check if all items in current question have been answered + bool get areAllTriageItemsAnswered { + 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)) { + return false; + } + } + return true; + } + /// Get organs for current view - List get currentOrgans => OrganData.getOrgansForView(_currentView); + List get currentOrgans => + OrganData.getOrgansForView(_currentView); /// Get all selected organs from both views List get selectedOrgans { @@ -126,7 +162,9 @@ 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 @@ -143,11 +181,13 @@ 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 @@ -173,7 +213,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier { } void toggleView() { - _currentView = _currentView == BodyView.front ? BodyView.back : BodyView.front; + _currentView = + _currentView == BodyView.front ? BodyView.back : BodyView.front; notifyListeners(); } @@ -276,7 +317,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier { return; } - List organNames = selectedOrgans.map((organ) => organ.name).toList(); + List organNames = + selectedOrgans.map((organ) => organ.name).toList(); await getBodySymptomsByName( organNames: organNames, @@ -326,7 +368,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier { } } - if (matchingOrganId != null && _selectedSymptomsByOrgan.containsKey(matchingOrganId)) { + if (matchingOrganId != null && + _selectedSymptomsByOrgan.containsKey(matchingOrganId)) { final selectedIds = _selectedSymptomsByOrgan[matchingOrganId]!; if (organResult.bodySymptoms != null) { @@ -377,7 +420,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier { /// Get all selected risk factors List 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 @@ -410,7 +456,8 @@ class SymptomsCheckerViewModel extends ChangeNotifier { } // Extract symptom IDs - List evidenceIds = selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList(); + List evidenceIds = + selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList(); await getRiskFactors( age: _selectedAge!, @@ -468,8 +515,10 @@ 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", @@ -523,7 +572,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier { /// Get all selected risk factors List 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 @@ -538,15 +590,20 @@ 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; } @@ -575,13 +632,17 @@ class SymptomsCheckerViewModel extends ChangeNotifier { } // Extract symptom IDs - List evidenceIds = selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList(); + List evidenceIds = + selectedSymptoms.where((s) => s.id != null).map((s) => s.id!).toList(); // Get all selected symptoms final selectedRisks = getAllSelectedRiskFactors(); if (selectedRisks.isNotEmpty) { - List evidenceRisksIds = selectedRisks.where((s) => s.id != null && s.id != "not_applicable").map((s) => s.id!).toList(); + List evidenceRisksIds = selectedRisks + .where((s) => s.id != null && s.id != "not_applicable") + .map((s) => s.id!) + .toList(); evidenceIds.addAll(evidenceRisksIds); } @@ -639,8 +700,10 @@ 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", @@ -669,6 +732,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { required int age, required String sex, required List evidenceIds, + List>? triageEvidence, required String language, Function(TriageDataDetails)? onSuccess, Function(String)? onError, @@ -680,6 +744,7 @@ class SymptomsCheckerViewModel extends ChangeNotifier { age: age, sex: sex, evidenceIds: evidenceIds, + triageEvidence: triageEvidence, language: language, sessionId: currentSessionId, userSessionToken: currentSessionAuthToken, @@ -755,15 +820,44 @@ class SymptomsCheckerViewModel extends ChangeNotifier { ); } - /// Select a triage choice option + /// Select a triage choice option (for backward compatibility with single-item questions) void selectTriageChoice(int choiceIndex) { _selectedTriageChoiceIndex = choiceIndex; notifyListeners(); } - /// Reset triage choice selection + /// Select a choice for a specific item (for multi-item questions) + void selectTriageChoiceForItem(String itemId, int choiceIndex) { + _selectedTriageChoicesByItemId[itemId] = choiceIndex; + notifyListeners(); + } + + /// Reset triage choice selection and increment question count void resetTriageChoice() { _selectedTriageChoiceIndex = null; + _selectedTriageChoicesByItemId.clear(); + _triageQuestionCount++; // Increment question count + notifyListeners(); + } + + /// Add triage evidence in the proper format + void addTriageEvidence(String itemId, String choiceId) { + _triageEvidenceList.add({ + "id": itemId, + "choice_id": choiceId, + "source": "triage", + }); + notifyListeners(); + } + + /// Get all triage evidence + List> getTriageEvidence() { + return List.from(_triageEvidenceList); + } + + /// Clear triage evidence + void clearTriageEvidence() { + _triageEvidenceList.clear(); notifyListeners(); } @@ -773,6 +867,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier { _selectedSymptomsByOrgan.clear(); _selectedRiskFactorIds.clear(); _selectedSuggestionsIds.clear(); + _triageEvidenceList.clear(); + _selectedTriageChoicesByItemId.clear(); + _triageQuestionCount = 0; // Reset question count bodySymptomResponse = null; riskFactorsResponse = null; suggestionsResponse = null; @@ -844,7 +941,8 @@ 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; @@ -891,7 +989,8 @@ 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 { diff --git a/lib/presentation/symptoms_checker/triage_screen.dart b/lib/presentation/symptoms_checker/triage_screen.dart index 8262c67..ba159bd 100644 --- a/lib/presentation/symptoms_checker/triage_screen.dart +++ b/lib/presentation/symptoms_checker/triage_screen.dart @@ -2,6 +2,7 @@ import 'dart:developer'; import 'package:easy_localization/easy_localization.dart'; 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/utils/utils.dart'; @@ -17,6 +18,7 @@ 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:lottie/lottie.dart'; import 'package:provider/provider.dart'; class TriageScreen extends StatefulWidget { @@ -27,7 +29,6 @@ class TriageScreen extends StatefulWidget { } class _TriageScreenState extends State { - List answeredEvidenceIds = []; // Track user's answers late SymptomsCheckerViewModel viewModel; late DialogService dialogService; @@ -58,43 +59,96 @@ class _TriageScreenState extends State { } void _handleTriageResponse() { + // Case 1: Emergency evidence detected if (viewModel.hasEmergencyEvidence) { _showEmergencyDialog(); return; } - if (viewModel.shouldStopTriage) { + // Get the highest probability condition + final conditions = viewModel.currentConditions ?? []; + double highestProbability = 0.0; + + if (conditions.isNotEmpty) { + final sortedConditions = List.from(conditions); + sortedConditions.sort((a, b) => (b.probability ?? 0.0).compareTo(a.probability ?? 0.0)); + highestProbability = (sortedConditions.first.probability ?? 0.0) * 100; + } + + // Case 2: Should stop flag is true OR Case 3: Probability >= 70% OR Case 4: 7 or more questions answered + if (viewModel.shouldStopTriage || highestProbability >= 70.0 || viewModel.triageQuestionCount >= 7) { // Navigate to results/possible conditions screen context.navigateWithName(AppRoutes.possibleConditionsScreen); return; } - // Question is loaded, reset selection for new question + // Continue triage - 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(); - }, + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.primaryRedColor, + borderRadius: 24.h, + ), + child: Padding( + padding: EdgeInsets.all(24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "".toText14(), + Utils.buildSvgWithAssets( + icon: AppAssets.cancel_circle_icon, + iconColor: AppColors.whiteColor, + width: 24.h, + height: 24.h, + fit: BoxFit.contain, + ).onPress(() { + Navigator.of(context).pop(); + }), + ], + ), + Lottie.asset(AppAnimations.ambulanceAlert, + repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain), + SizedBox(height: 8.h), + "Emergency".needTranslation.toText28(color: AppColors.whiteColor, isBold: true), + SizedBox(height: 8.h), + "Emergency evidence detected. Please seek medical attention." + .needTranslation + .toText14(color: AppColors.whiteColor, weight: FontWeight.w500), + SizedBox(height: 24.h), + CustomButton( + text: LocaleKeys.confirm.tr(context: context), + onPressed: () async => Navigator.of(context).pop(), + backgroundColor: AppColors.whiteColor, + borderColor: AppColors.whiteColor, + textColor: AppColors.primaryRedColor, + icon: AppAssets.checkmark_icon, + iconColor: AppColors.primaryRedColor, + ), + SizedBox(height: 8.h), + ], + ), + ), ), isFullScreen: false, - isCloseButtonVisible: true, + isCloseButtonVisible: false, + hasBottomPadding: false, + backgroundColor: AppColors.primaryRedColor, + callBackFunc: () {}, ); } - bool get isFirstQuestion => answeredEvidenceIds.isEmpty; + bool get isFirstQuestion => viewModel.getTriageEvidence().isEmpty; - void _onOptionSelected(int choiceIndex) { - viewModel.selectTriageChoice(choiceIndex); + void _onOptionSelectedForItem(String itemId, int choiceIndex) { + viewModel.selectTriageChoiceForItem(itemId, choiceIndex); } void _onPreviousPressed() { @@ -102,13 +156,6 @@ class _TriageScreenState extends State { } 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( @@ -117,32 +164,43 @@ class _TriageScreenState extends State { return; } - final questionItem = currentQuestion.items!.first; - if (questionItem.choices == null || viewModel.selectedTriageChoiceIndex! >= questionItem.choices!.length) { - dialogService.showErrorBottomSheet( - message: 'Invalid choice selection'.needTranslation, - ); + // Check if all items have been answered + if (!viewModel.areAllTriageItemsAnswered) { + dialogService.showErrorBottomSheet(message: 'Please answer all questions before proceeding'.needTranslation); return; } - final selectedChoice = questionItem.choices![viewModel.selectedTriageChoiceIndex!]; + // Collect all evidence from all items + for (var item in currentQuestion.items!) { + final itemId = item.id ?? ""; + if (itemId.isEmpty) continue; - final evidenceId = selectedChoice.label ?? ""; - if (evidenceId.isNotEmpty) { - answeredEvidenceIds.add(evidenceId); + final selectedChoiceIndex = viewModel.getTriageChoiceForItem(itemId); + if (selectedChoiceIndex == null) continue; + + if (item.choices != null && selectedChoiceIndex < item.choices!.length) { + final selectedChoice = item.choices![selectedChoiceIndex]; + final choiceId = selectedChoice.id ?? ""; + + if (choiceId.isNotEmpty) { + viewModel.addTriageEvidence(itemId, choiceId); + } + } } - // Get all previous evidence IDs - List allEvidenceIds = viewModel.getAllEvidenceIds(); - allEvidenceIds.addAll(answeredEvidenceIds); + // Get all evidence: initial symptoms + risk factors + suggestions + triage evidence + List initialEvidenceIds = viewModel.getAllEvidenceIds(); + List> triageEvidence = viewModel.getTriageEvidence(); - log("allEvidences: ${allEvidenceIds.toString()}"); + log("initialEvidenceIds: ${initialEvidenceIds.toString()}"); + log("triageEvidence: ${triageEvidence.toString()}"); // Call API with updated evidence viewModel.getDiagnosisForTriage( age: viewModel.selectedAge!, sex: viewModel.selectedGender!.toLowerCase(), - evidenceIds: allEvidenceIds, + evidenceIds: initialEvidenceIds, + triageEvidence: triageEvidence, language: viewModel.appState.isArabic() ? 'ar' : 'en', onSuccess: (response) { _handleTriageResponse(); @@ -241,29 +299,6 @@ class _TriageScreenState extends State { ); } - 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), @@ -299,9 +334,6 @@ class _TriageScreenState extends State { return SizedBox.shrink(); } - final questionItem = question.items!.first; - final choices = questionItem.choices ?? []; - return AnimatedSwitcher( duration: const Duration(milliseconds: 400), transitionBuilder: (Widget child, Animation animation) { @@ -324,7 +356,7 @@ class _TriageScreenState extends State { ); }, child: Container( - key: ValueKey(questionItem.id ?? answeredEvidenceIds.length.toString()), + key: ValueKey(question.items!.first.id ?? viewModel.getTriageEvidence().length.toString()), width: double.infinity, margin: EdgeInsets.symmetric(horizontal: 24.w), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), @@ -332,11 +364,36 @@ class _TriageScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - (question.text ?? "").toText16(weight: FontWeight.w500), + // Main question text + (question.text ?? "").toText16(weight: FontWeight.w600, color: AppColors.textColor), SizedBox(height: 24.h), - ...List.generate(choices.length, (index) { - bool selected = viewModel.selectedTriageChoiceIndex == index; - return _buildOptionItem(index, selected, choices[index].label ?? ""); + + // Show all items with dividers + ...List.generate(question.items!.length, (itemIndex) { + final item = question.items![itemIndex]; + final itemId = item.id ?? ""; + final choices = item.choices ?? []; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Item name (sub-question) + (item.name ?? "").toText14(weight: FontWeight.w600, color: AppColors.textColor), + SizedBox(height: 8.h), + // Choices for this item + ...List.generate(choices.length, (choiceIndex) { + bool selected = viewModel.getTriageChoiceForItem(itemId) == choiceIndex; + return _buildOptionItem(itemId, choiceIndex, selected, choices[choiceIndex].label ?? ""); + }), + + // Add divider between items (but not after the last one) + if (itemIndex < question.items!.length - 1) ...[ + SizedBox(height: 8.h), + Divider(color: AppColors.bottomNAVBorder, thickness: 1), + SizedBox(height: 10.h), + ], + ], + ); }), ], ), @@ -344,9 +401,9 @@ class _TriageScreenState extends State { ); } - Widget _buildOptionItem(int index, bool selected, String optionText) { + Widget _buildOptionItem(String itemId, int choiceIndex, bool selected, String optionText) { return GestureDetector( - onTap: () => _onOptionSelected(index), + onTap: () => _onOptionSelectedForItem(itemId, choiceIndex), child: Container( margin: EdgeInsets.only(bottom: 12.h), child: Row( @@ -365,7 +422,7 @@ class _TriageScreenState extends State { child: selected ? Icon(Icons.check, size: 16.f, color: AppColors.whiteColor) : null, ), SizedBox(width: 12.w), - Expanded(child: optionText.toText14(weight: FontWeight.w500)), + Expanded(child: optionText.toText13(weight: FontWeight.w500)), ], ), ), @@ -387,6 +444,7 @@ class _TriageScreenState extends State { suggestedCondition = topCondition.commonName ?? topCondition.name ?? "Unknown"; probability = (topCondition.probability ?? 0.0) * 100; // Convert to percentage } + // final bool isHighConfidence = probability >= 70.0; return Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), @@ -445,6 +503,8 @@ class _TriageScreenState extends State { ), ), ), + // Show high confidence message + SizedBox(height: 12.h), Row( children: [ diff --git a/services/api.ts b/services/api.ts deleted file mode 100644 index 8b13789..0000000 --- a/services/api.ts +++ /dev/null @@ -1 +0,0 @@ - diff --git a/types/user.ts b/types/user.ts deleted file mode 100644 index a2158ef..0000000 --- a/types/user.ts +++ /dev/null @@ -1,57 +0,0 @@ -export interface UserName { - first_name: string; - middle_name: string; - last_name: string; - first_name_ar: string; - middle_name_ar: string; - last_name_ar: string; -} - -export interface UserDetails { - FileNo: string; - national_id: string; - email: string; - date_of_birth: string; - date_of_birth_hijri: string | null; - age: number; - name: UserName; - marital_status: number; - marital_status_code: string | null; - nationality: string; - nationality_iso_code: string; - occupation: string | null; - id_type: number; - gender: number; - jwt_token: string | null; - country_dial_code: string; - phone_no: string; -} - -export interface TokenDetails { - id: string; - auth_token: string; - expires_in: number; -} - -export interface GetUserDetailsResponse { - tokenDetails: TokenDetails; - userDetails: UserDetails; - sessionId: string; -} - -// Simplified User type for app usage -export interface User { - id: string; - fileNo: string; - nationalId: string; - email: string; - name: string; - nameAr: string; - phoneNo: string; - dateOfBirth: string; - nationality: string; - gender: number; - authToken: string; - sessionId: string; -} - diff --git a/utils/apiHelpers.ts b/utils/apiHelpers.ts deleted file mode 100644 index 3fae819..0000000 --- a/utils/apiHelpers.ts +++ /dev/null @@ -1,18 +0,0 @@ -export function safeJsonParse(data: any): T { - if (typeof data === 'string') { - try { - return JSON.parse(data); - } catch (error) { - console.error('Failed to parse JSON string:', error); - throw new Error('Invalid JSON response from server'); - } - } - return data; -} - -export function logApiResponse(endpoint: string, response: any) { - if (__DEV__) { - console.log(`[API Response - ${endpoint}]`, JSON.stringify(response, null, 2)); - } -} -