@ -5,9 +5,11 @@ import 'package:hmg_patient_app_new/core/app_state.dart';
import ' package:hmg_patient_app_new/core/enums.dart ' ;
import ' package:hmg_patient_app_new/features/symptoms_checker/data/organ_mapping_data.dart ' ;
import ' package:hmg_patient_app_new/features/symptoms_checker/models/organ_model.dart ' ;
import ' package:hmg_patient_app_new/features/symptoms_checker/models/req_models/schedule_appointment_request_model.dart ' ;
import ' package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart ' ;
import ' package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/get_clinic_details_response_model.dart ' ;
import ' package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/risk_and_suggestions_response_model.dart ' ;
import ' package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/schedule_appointment_response_model.dart ' ;
import ' package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/symptoms_user_details_response_model.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_repo.dart ' ;
@ -62,6 +64,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
bool isRiskFactorsLoading = false ;
bool isSuggestionsLoading = false ;
bool isTriageDiagnosisLoading = false ;
bool isScheduleAppointmentLoading = false ;
/ / Flag to track if appointment is being booked via symptoms checker flow
bool isBookingFromSymptomsChecker = false ;
/ / API data storage - using API models directly
SymptomsUserDetailsResponseModel ? symptomsUserDetailsResponseModel ;
@ -89,6 +95,10 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
/ / Selected symptoms tracking ( organId - > Set of symptom IDs )
final Map < String , Set < String > > _selectedSymptomsByOrgan = { } ;
/ / Symptom search / filter state
String _symptomSearchQuery = ' ' ;
List < OrganSymptomResult > _filteredOrganSymptomsResults = [ ] ;
/ / User Info Flow State
int _userInfoCurrentPage = 0 ;
bool _isSinglePageEditMode = false ; / / Track if editing single page or full flow
@ -180,9 +190,9 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
/ / / Check if current question type is multi - item selection ( type = 2 )
bool get isTriageQuestionMultiItem = > currentTriageQuestion ? . type = = 2 ;
/ / / For type = 1 questions: check if a specific item - choice combination is selected
/ / / For type = 1 questions: check if a specific item is selected ( ignore choiceIndex )
bool isTriageSingleOptionSelected ( String itemId , int choiceIndex ) {
return _selectedSingleItemId = = itemId & & _selectedSingleChoiceIndex = = choiceIndex ;
return _selectedSingleItemId = = itemId ;
}
/ / / Get selected item ID for type = 1 questions
@ -191,6 +201,12 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
/ / / Get selected choice index for type = 1 questions
int ? get selectedSingleChoiceIndex = > _selectedSingleChoiceIndex ;
/ / / Set flag for booking from symptoms checker
void setBookingFromSymptomsChecker ( bool value ) {
isBookingFromSymptomsChecker = value ;
notifyListeners ( ) ;
}
/ / / Check if all items in current question have been answered
bool get areAllTriageItemsAnswered {
if ( currentTriageQuestion ? . items = = null | | currentTriageQuestion ! . items ! . isEmpty ) {
@ -234,6 +250,28 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
return bodySymptomResponse ! . dataDetails ! . result ? ? [ ] ;
}
/ / / Get filtered organ symptoms results based on search query
List < OrganSymptomResult > get filteredOrganSymptomsResults {
if ( _symptomSearchQuery . isEmpty ) {
return organSymptomsResults ;
}
return _filteredOrganSymptomsResults ;
}
/ / / Get current search query
String get symptomSearchQuery = > _symptomSearchQuery ;
/ / / Get all symptoms from all organs ( for search suggestions )
List < BodySymptom > get allSymptoms {
List < BodySymptom > symptoms = [ ] ;
for ( var organResult in organSymptomsResults ) {
if ( organResult . bodySymptoms ! = null ) {
symptoms . addAll ( organResult . bodySymptoms ! ) ;
}
}
return symptoms ;
}
int get totalSelectedSymptomsCount {
return _selectedSymptomsByOrgan . values . fold ( 0 , ( sum , symptomIds ) = > sum + symptomIds . length ) ;
}
@ -454,6 +492,51 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
notifyListeners ( ) ;
}
/ / / Filter symptoms based on search query
void filterSymptoms ( String query , { bool isArabic = false } ) {
_symptomSearchQuery = query ;
if ( query . isEmpty ) {
_filteredOrganSymptomsResults . clear ( ) ;
notifyListeners ( ) ;
return ;
}
final lowercaseQuery = query . toLowerCase ( ) ;
_filteredOrganSymptomsResults = [ ] ;
for ( var organResult in organSymptomsResults ) {
if ( organResult . bodySymptoms = = null | | organResult . bodySymptoms ! . isEmpty ) {
continue ;
}
/ / Filter symptoms that match the query
final filteredSymptoms = organResult . bodySymptoms ! . where ( ( symptom ) {
final displayName = symptom . getDisplayName ( isArabic ) . toLowerCase ( ) ;
return displayName . contains ( lowercaseQuery ) ;
} ) . toList ( ) ;
/ / Only add organ result if it has matching symptoms
if ( filteredSymptoms . isNotEmpty ) {
_filteredOrganSymptomsResults . add (
OrganSymptomResult (
name: organResult . name ,
bodySymptoms: filteredSymptoms ,
) ,
) ;
}
}
notifyListeners ( ) ;
}
/ / / Clear symptom search filter
void clearSymptomFilter ( ) {
_symptomSearchQuery = ' ' ;
_filteredOrganSymptomsResults . clear ( ) ;
notifyListeners ( ) ;
}
/ / Risk Factors Methods
/ / / Toggle risk factor selection
@ -873,16 +956,17 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
/ / / Select a choice for a specific item ( for multi - item questions )
void selectTriageChoiceForItem ( String itemId , int choiceIndex ) {
/ / Type 1 : Single selection mode - only one option can be selected across all items
/ / Type 1 : Single selection mode - only one ITEM can be selected ( choice is always " Yes " )
if ( isTriageQuestionSingleSelection ) {
/ / If same option clicked again , deselect it
if ( _selectedSingleItemId = = itemId & & _selectedSingleChoiceIndex = = choiceIndex ) {
/ / If same item clicked again , deselect it
if ( _selectedSingleItemId = = itemId ) {
_selectedSingleItemId = null ;
_selectedSingleChoiceIndex = null ;
} else {
/ / Select new option , clear previous selection
/ / Select new item , clear previous selection
_selectedSingleItemId = itemId ;
_selectedSingleChoiceIndex = choiceIndex ;
/ / For type = 1 , we don ' t use choiceIndex from UI, we ' ll find " Yes " choice in the API call
_selectedSingleChoiceIndex = 0 ; / / Placeholder , actual Yes choice will be found later
}
} else {
/ / Type 2 : Multi - item selection mode - each item can have one selected option
@ -932,15 +1016,20 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
_selectedTriageChoicesByItemId . clear ( ) ;
_triageQuestionCount = 0 ; / / Reset question count
_currentZoomScale = 1.0 ; / / Reset zoom scale
_symptomSearchQuery = ' ' ; / / Reset search query
_filteredOrganSymptomsResults . clear ( ) ; / / Clear filtered results
bodySymptomResponse = null ;
riskFactorsResponse = null ;
suggestionsResponse = null ;
triageDataDetails = null ;
isTriageDiagnosisLoading = false ;
_selectedTriageChoiceIndex = null ;
_selectedSingleItemId = null ;
_selectedSingleChoiceIndex = null ;
_isBottomSheetExpanded = false ;
_tooltipTimer ? . cancel ( ) ;
_tooltipOrganId = null ;
isBookingFromSymptomsChecker = false ; / / Reset booking flag
/ / Reset user info flow
_userInfoCurrentPage = 0 ;
_isSinglePageEditMode = false ;
@ -1179,6 +1268,67 @@ class SymptomsCheckerViewModel extends ChangeNotifier {
) ;
}
/ / / Schedule appointment for symptoms checker
Future < void > saveAppointmentDetailsForSymptomsChecker ( {
required String fileNo ,
required String appointmentNo ,
required String doctorId ,
required String appointmentDate ,
required String mobileNumber ,
required int projectId ,
required int clinicId ,
Function ( ScheduleAppointmentResponseModel ) ? onSuccess ,
Function ( String ) ? onError ,
} ) async {
isScheduleAppointmentLoading = true ;
notifyListeners ( ) ;
/ / Import the request model at the top of the file
final request = ScheduleAppointmentRequestModel (
generalId: currentSessionId ,
fileNo: fileNo ,
appointmentNo: appointmentNo ,
doctorId: doctorId ,
appointmentDate: appointmentDate ,
mobileNumber: mobileNumber ,
projectId: projectId ,
clinicId: clinicId ,
) ;
final result = await symptomsCheckerRepo . saveAppointmentDetailsForSymptomsChecker (
request: request ,
userSessionToken: currentSessionAuthToken ,
) ;
result . fold (
( failure ) async {
isScheduleAppointmentLoading = false ;
isBookingFromSymptomsChecker = false ; / / Reset flag on error
notifyListeners ( ) ;
await errorHandlerService . handleError ( failure: failure ) ;
if ( onError ! = null ) {
onError ( failure . toString ( ) ) ;
}
} ,
( apiResponse ) {
isScheduleAppointmentLoading = false ;
if ( apiResponse . messageStatus = = 1 & & apiResponse . data ! = null ) {
isBookingFromSymptomsChecker = false ; / / Reset flag on success
notifyListeners ( ) ;
if ( onSuccess ! = null ) {
onSuccess ( apiResponse . data ! ) ;
}
} else {
isBookingFromSymptomsChecker = false ; / / Reset flag on error
notifyListeners ( ) ;
if ( onError ! = null ) {
onError ( apiResponse . errorMessage ? ? ' Failed to schedule appointment ' ) ;
}
}
} ,
) ;
}
@ override
void dispose ( ) {
_tooltipTimer ? . cancel ( ) ;