Triage Completed. Explain API left

faiz_dev
faizatflutter 1 week ago
parent 49d091547a
commit 8c91b6830a

@ -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...
};

@ -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);

@ -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<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 List<String> evidenceIds,
@ -33,7 +36,8 @@ abstract class SymptomsCheckerRepo {
required String sessionId,
});
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>> getSuggestions({
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>>
getSuggestions({
required int age,
required String sex,
required List<String> evidenceIds,
@ -43,10 +47,12 @@ abstract class SymptomsCheckerRepo {
required int gender,
});
Future<Either<Failure, GenericApiModel<TriageDataDetails>>> getDiagnosisForTriage({
Future<Either<Failure, GenericApiModel<TriageDataDetails>>>
getDiagnosisForTriage({
required int age,
required String sex,
required List<String> evidenceIds,
List<Map<String, String>>? 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<Either<Failure, GenericApiModel<SymptomsUserDetailsResponseModel>>> getUserDetails({
Future<Either<Failure, GenericApiModel<SymptomsUserDetailsResponseModel>>>
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<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,
@ -113,7 +123,8 @@ 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,
@ -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<BodySymptomResponseModel>(
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<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>> getRiskFactors({
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>>
getRiskFactors({
required int age,
required String sex,
required List<String> evidenceIds,
@ -211,9 +225,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;
RiskAndSuggestionsResponseModel riskFactorsResponse = RiskAndSuggestionsResponseModel.fromJson(responseData);
RiskAndSuggestionsResponseModel riskFactorsResponse =
RiskAndSuggestionsResponseModel.fromJson(responseData);
apiResponse = GenericApiModel<RiskAndSuggestionsResponseModel>(
messageStatus: messageStatus ?? 1,
@ -239,22 +255,37 @@ class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
}
}
@override
Future<Either<Failure, GenericApiModel<TriageDataDetails>>> getDiagnosisForTriage({
Future<Either<Failure, GenericApiModel<TriageDataDetails>>>
getDiagnosisForTriage({
required int age,
required String sex,
required List<String> evidenceIds,
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
List<Map<String, dynamic>> 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<String, dynamic> 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<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,
@ -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<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>> getSuggestions({
Future<Either<Failure, GenericApiModel<RiskAndSuggestionsResponseModel>>>
getSuggestions({
required int age,
required String sex,
required List<String> evidenceIds,
@ -354,9 +389,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;
RiskAndSuggestionsResponseModel riskFactorsResponse = RiskAndSuggestionsResponseModel.fromJson(responseData);
RiskAndSuggestionsResponseModel riskFactorsResponse =
RiskAndSuggestionsResponseModel.fromJson(responseData);
apiResponse = GenericApiModel<RiskAndSuggestionsResponseModel>(
messageStatus: messageStatus ?? 1,

@ -47,7 +47,13 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
TriageDataDetails? triageDataDetails;
// Triage state
int? _selectedTriageChoiceIndex;
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
final Set<String> _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<OrganModel> get currentOrgans => OrganData.getOrgansForView(_currentView);
List<OrganModel> get currentOrgans =>
OrganData.getOrgansForView(_currentView);
/// Get all selected organs from both views
List<OrganModel> 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<String> organNames = selectedOrgans.map((organ) => organ.name).toList();
List<String> 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<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
@ -410,7 +456,8 @@ 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!,
@ -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<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
@ -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<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);
}
@ -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<String> evidenceIds,
List<Map<String, String>>? 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<Map<String, String>> 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 {

@ -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<TriageScreen> {
List<String> answeredEvidenceIds = []; // Track user's answers
late SymptomsCheckerViewModel viewModel;
late DialogService dialogService;
@ -58,43 +59,96 @@ class _TriageScreenState extends State<TriageScreen> {
}
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<TriageCondition>.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<TriageScreen> {
}
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<TriageScreen> {
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<String> allEvidenceIds = viewModel.getAllEvidenceIds();
allEvidenceIds.addAll(answeredEvidenceIds);
// Get all evidence: initial symptoms + risk factors + suggestions + triage evidence
List<String> initialEvidenceIds = viewModel.getAllEvidenceIds();
List<Map<String, String>> 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<TriageScreen> {
);
}
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<TriageScreen> {
return SizedBox.shrink();
}
final questionItem = question.items!.first;
final choices = questionItem.choices ?? [];
return AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
transitionBuilder: (Widget child, Animation<double> animation) {
@ -324,7 +356,7 @@ class _TriageScreenState extends State<TriageScreen> {
);
},
child: Container(
key: ValueKey<String>(questionItem.id ?? answeredEvidenceIds.length.toString()),
key: ValueKey<String>(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<TriageScreen> {
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<TriageScreen> {
);
}
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<TriageScreen> {
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<TriageScreen> {
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<TriageScreen> {
),
),
),
// Show high confidence message
SizedBox(height: 12.h),
Row(
children: [

@ -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;
}

@ -1,18 +0,0 @@
export function safeJsonParse<T>(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));
}
}
Loading…
Cancel
Save