Integrated the API for Symptoms Checker

pull/113/head
faizatflutter 3 months ago
parent e35fe9ac05
commit 2ffd4cc1ae

File diff suppressed because it is too large Load Diff

@ -0,0 +1,73 @@
import 'dart:convert';
import 'dart:io';
void main() async {
final file = File('assets/json/body_symptoms_data.json');
final content = await file.readAsString();
print('File size: ${content.length} characters');
// Split into two parts
final parts = content.split('export const SymptomsData = [');
if (parts.length != 2) {
print('ERROR: Could not split file properly');
exit(1);
}
print('Split into ${parts.length} parts');
// Process BodySymptomsData
var bodyPart = parts[0]
.replaceAll('export const BodySymptomsData = ', '')
.trim()
.replaceAll(RegExp(r';$'), '');
print('Parsing body symptoms...');
Map<String, dynamic> bodySymptomsData;
try {
bodySymptomsData = json.decode(bodyPart);
print('✅ Body symptoms parsed: ${bodySymptomsData.length} body parts');
} catch (e) {
print('❌ Error parsing body symptoms: $e');
exit(1);
}
// Process SymptomsData
print('Processing symptoms data...');
var symptomsPart = parts[1]
.trim()
.replaceAll(RegExp(r'\];$'), '')
.replaceAllMapped(RegExp(r'\n(\s*)id:'), (m) => '\n${m.group(1)}"id":')
.replaceAllMapped(RegExp(r'\n(\s*)type:'), (m) => '\n${m.group(1)}"type":')
.replaceAllMapped(RegExp(r'\n(\s*)name:'), (m) => '\n${m.group(1)}"name":')
.replaceAllMapped(RegExp(r'\n(\s*)common_name:'), (m) => '\n${m.group(1)}"common_name":');
print('Parsing symptoms...');
List<dynamic> symptomsData;
try {
symptomsData = json.decode('[$symptomsPart]');
print('✅ Symptoms parsed: ${symptomsData.length} symptoms');
} catch (e) {
print('❌ Error parsing symptoms: $e');
print('First 1000 chars:');
print('[$symptomsPart]'.substring(0, 1000));
exit(1);
}
// Create final JSON
print('Creating final JSON structure...');
final jsonStructure = {
'bodySymptoms': bodySymptomsData,
'symptoms': symptomsData,
};
// Write to file
print('Writing to file...');
final encoder = JsonEncoder.withIndent(' ');
await file.writeAsString(encoder.convert(jsonStructure));
print('\n✅ SUCCESS! File converted to proper JSON');
print('✅ Body parts: ${bodySymptomsData.length}');
print('✅ Symptoms: ${symptomsData.length}');
}

@ -14,7 +14,7 @@ var PACKAGES_ORDERS = '/api/orders';
var PACKAGES_ORDER_HISTORY = '/api/orders/items'; var PACKAGES_ORDER_HISTORY = '/api/orders/items';
var PACKAGES_TAMARA_OPT = '/api/orders/paymentoptions/tamara'; var PACKAGES_TAMARA_OPT = '/api/orders/paymentoptions/tamara';
// var BASE_URL = 'http://10.50.100.198:2018/'; // var BASE_URL = 'http://10.50.100.198:2018/';
var BASE_URL = 'https://uat.hmgwebservices.com/'; var BASE_URL = 'https://uat.hmgwebservices.com/';
// var BASE_URL = 'https://hmgwebservices.com/'; // var BASE_URL = 'https://hmgwebservices.com/';
// var BASE_URL = 'http://10.201.204.103/'; // var BASE_URL = 'http://10.201.204.103/';
// var BASE_URL = 'https://orash.cloudsolutions.com.sa/'; // var BASE_URL = 'https://orash.cloudsolutions.com.sa/';
@ -181,6 +181,7 @@ var GET_TABLE_REQUEST = 'Services/Community.svc/REST/CreateVaccinationTable';
/// ///
/// use get all cities from the e-referral, already calling this api there don't use multiple same api calls /// use get all cities from the e-referral, already calling this api there don't use multiple same api calls
var GET_CITIES_REQUEST = 'Services/Lists.svc/REST/GetAllCities'; var GET_CITIES_REQUEST = 'Services/Lists.svc/REST/GetAllCities';
/// ///
/// ///
/// ///
@ -444,8 +445,6 @@ var H2O_UPDATE_USER_DETAIL = "Services/H2ORemainder.svc/REST/H2O_UpdateUserDetai
var H2O_UNDO_USER_ACTIVITY = "Services/H2ORemainder.svc/REST/H2o_UndoUserActivity"; var H2O_UNDO_USER_ACTIVITY = "Services/H2ORemainder.svc/REST/H2o_UndoUserActivity";
//E_Referral Services //E_Referral Services
// Encillary Orders // Encillary Orders
var GET_ANCILLARY_ORDERS = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderList'; var GET_ANCILLARY_ORDERS = 'Services/Doctors.svc/REST/GetOnlineAncillaryOrderList';
@ -710,7 +709,9 @@ class ApiConsts {
static String baseUrl = 'https://hmgwebservices.com/'; // HIS API URL PROD static String baseUrl = 'https://hmgwebservices.com/'; // HIS API URL PROD
static String rcBaseUrl = 'https://rc.hmg.com/'; // RC API URL PROD static String rcBaseUrl = 'https://rc.hmg.com/'; // dRC API URL PROD
static String hmgPharmacyApiBaseUrl = 'https://hmgpharmacyapi.hmg.com/'; // dRC API URL PROD
static String symptomsCheckerApi = '${hmgPharmacyApiBaseUrl}symptomsapi/api/SymptomChecker'; // dRC API URL PROD
static var payFortEnvironment = FortEnvironment.production; static var payFortEnvironment = FortEnvironment.production;
static var applePayMerchantId = "merchant.com.hmgwebservices"; static var applePayMerchantId = "merchant.com.hmgwebservices";
@ -844,6 +845,12 @@ class ApiConsts {
static final String updateHHCOrder = 'api/hhc/update'; static final String updateHHCOrder = 'api/hhc/update';
static final String addHHCOrder = 'api/HHC/add'; static final String addHHCOrder = 'api/HHC/add';
// SYMPTOMS CHECKER
static final String getBodySymptomsByName = '$symptomsCheckerApi/GetBodySymptomsByName';
static final String getRiskFactors = '$symptomsCheckerApi/GetRiskFactors';
static final String getGeneralSuggestion = '$symptomsCheckerApi/GetGeneralSggestion';
static final String diagnosis = '$symptomsCheckerApi/diagnosis';
static final String explain = '$symptomsCheckerApi/explain';
//E-REFERRAL SERVICES //E-REFERRAL SERVICES
@ -854,7 +861,6 @@ class ApiConsts {
static final createEReferral = "Services/Patients.svc/REST/CreateEReferral"; static final createEReferral = "Services/Patients.svc/REST/CreateEReferral";
static final getEReferrals = "Services/Patients.svc/REST/GetEReferrals"; static final getEReferrals = "Services/Patients.svc/REST/GetEReferrals";
// ************ static values for Api **************** // ************ static values for Api ****************
static final double appVersionID = 50.3; static final double appVersionID = 50.3;
static final int appChannelId = 3; static final int appChannelId = 3;

@ -16,6 +16,7 @@ import 'package:hmg_patient_app_new/features/emergency_services/emergency_servic
import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_repo.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_repo.dart';
import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_repo.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_repo.dart';
import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_repo.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_repo.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart';
import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart';
@ -36,9 +37,10 @@ import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_mo
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart';
import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart';
import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_repo.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_repo.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart';
@ -94,6 +96,9 @@ class AppDependencies {
final sharedPreferences = await SharedPreferences.getInstance(); final sharedPreferences = await SharedPreferences.getInstance();
getIt.registerLazySingleton<CacheService>(() => CacheServiceImp(sharedPreferences: sharedPreferences, loggerService: getIt())); getIt.registerLazySingleton<CacheService>(() => CacheServiceImp(sharedPreferences: sharedPreferences, loggerService: getIt()));
getIt.registerLazySingleton<ApiClient>(() => ApiClientImp(appState: getIt())); getIt.registerLazySingleton<ApiClient>(() => ApiClientImp(appState: getIt()));
getIt.registerLazySingleton<LocalAuthService>(
() => LocalAuthService(loggerService: getIt<LoggerService>(), localAuth: getIt<LocalAuthentication>()),
);
// Repositories // Repositories
getIt.registerLazySingleton<CommonRepo>(() => CommonRepoImp(loggerService: getIt())); getIt.registerLazySingleton<CommonRepo>(() => CommonRepoImp(loggerService: getIt()));
@ -105,9 +110,6 @@ class AppDependencies {
getIt.registerLazySingleton<PrescriptionsRepo>(() => PrescriptionsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt())); getIt.registerLazySingleton<PrescriptionsRepo>(() => PrescriptionsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<InsuranceRepo>(() => InsuranceRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt())); getIt.registerLazySingleton<InsuranceRepo>(() => InsuranceRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<PayfortRepo>(() => PayfortRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt())); getIt.registerLazySingleton<PayfortRepo>(() => PayfortRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<LocalAuthService>(
() => LocalAuthService(loggerService: getIt<LoggerService>(), localAuth: getIt<LocalAuthentication>()),
);
getIt.registerLazySingleton<HabibWalletRepo>(() => HabibWalletRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt())); getIt.registerLazySingleton<HabibWalletRepo>(() => HabibWalletRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<MedicalFileRepo>(() => MedicalFileRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt())); getIt.registerLazySingleton<MedicalFileRepo>(() => MedicalFileRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<ImmediateLiveCareRepo>(() => ImmediateLiveCareRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt())); getIt.registerLazySingleton<ImmediateLiveCareRepo>(() => ImmediateLiveCareRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
@ -116,6 +118,7 @@ class AppDependencies {
getIt.registerLazySingleton<LocationRepo>(() => LocationRepoImpl(apiClient: getIt())); getIt.registerLazySingleton<LocationRepo>(() => LocationRepoImpl(apiClient: getIt()));
getIt.registerLazySingleton<ContactUsRepo>(() => ContactUsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt())); getIt.registerLazySingleton<ContactUsRepo>(() => ContactUsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<HmgServicesRepo>(() => HmgServicesRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt())); getIt.registerLazySingleton<HmgServicesRepo>(() => HmgServicesRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<SymptomsCheckerRepo>(() => SymptomsCheckerRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
// ViewModels // ViewModels
// Global/shared VMs LazySingleton // Global/shared VMs LazySingleton
@ -126,7 +129,8 @@ class AppDependencies {
() => RadiologyViewModel(radiologyRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()), () => RadiologyViewModel(radiologyRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()),
); );
getIt.registerLazySingleton<PrescriptionsViewModel>(() => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt(), navServices: getIt())); getIt.registerLazySingleton<PrescriptionsViewModel>(
() => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt(), navServices: getIt()));
getIt.registerLazySingleton<InsuranceViewModel>(() => InsuranceViewModel(insuranceRepo: getIt(), errorHandlerService: getIt())); getIt.registerLazySingleton<InsuranceViewModel>(() => InsuranceViewModel(insuranceRepo: getIt(), errorHandlerService: getIt()));
@ -216,7 +220,7 @@ class AppDependencies {
() => TodoSectionViewModel(todoSectionRepo: getIt(), errorHandlerService: getIt()), () => TodoSectionViewModel(todoSectionRepo: getIt(), errorHandlerService: getIt()),
); );
getIt.registerLazySingleton<SymptomsCheckerViewModel>(() => SymptomsCheckerViewModel()); getIt.registerLazySingleton<SymptomsCheckerViewModel>(() => SymptomsCheckerViewModel(errorHandlerService: getIt(), symptomsCheckerRepo: getIt()));
getIt.registerLazySingleton<HmgServicesViewModel>( getIt.registerLazySingleton<HmgServicesViewModel>(
() => HmgServicesViewModel(bookAppointmentsRepo: getIt(), hmgServicesRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()), () => HmgServicesViewModel(bookAppointmentsRepo: getIt(), hmgServicesRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()),
); );

@ -25,7 +25,13 @@ class CalenderUtilsNew {
}); });
} }
FutureOr<bool> createOrUpdateEvent({required String title, required String description, required String location, DateTime? scheduleDateTime, String? eventId, int? reminderMinutes}) async { FutureOr<bool> createOrUpdateEvent(
{required String title,
required String description,
required String location,
DateTime? scheduleDateTime,
String? eventId,
int? reminderMinutes}) async {
if (writableCalender.isEmpty) { if (writableCalender.isEmpty) {
await getCalenders(); await getCalenders();
} }
@ -38,12 +44,14 @@ class CalenderUtilsNew {
// String eventId = await getEventIdIfEventExist(title!.split("#").last); // String eventId = await getEventIdIfEventExist(title!.split("#").last);
// if (eventId.isEmpty) { // if (eventId.isEmpty) {
eventResult = await DeviceCalendar.instance.createEvent( eventResult = await DeviceCalendar.instance.createEvent(
calendarId: writableCalendars!.id, calendarId: writableCalendars!.id,
title: title, title: title,
description: description, description: description,
startDate: scheduleDateTime!, startDate: scheduleDateTime!,
endDate: scheduleDateTime!.add(Duration(minutes: 30)), endDate: scheduleDateTime!.add(Duration(minutes: 30)),
reminderMinutes: reminderMinutes);
// reminderMinutes: reminderMinutes, // TODO : NEED TO CONFIRM THIS FROM TAHA
);
return eventResult.isNotEmpty; return eventResult.isNotEmpty;
// } // }

@ -8,7 +8,6 @@ import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:crypto/crypto.dart' as crypto; import 'package:crypto/crypto.dart' as crypto;
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:google_api_availability/google_api_availability.dart'; import 'package:google_api_availability/google_api_availability.dart';
@ -871,7 +870,8 @@ class Utils {
} }
return isHavePrivilege; return isHavePrivilege;
} }
static void openWebView({ required String url}) {
static void openWebView({required String url}) {
Uri uri = Uri.parse(url); Uri uri = Uri.parse(url);
launchUrl(uri, mode: LaunchMode.inAppBrowserView); launchUrl(uri, mode: LaunchMode.inAppBrowserView);
} }

@ -7,25 +7,29 @@ class OrganData {
// ========== HEAD & FACE ========== // ========== HEAD & FACE ==========
OrganModel( OrganModel(
id: 'head', id: 'head',
name: 'Head', name: 'whole_head',
description: 'Head',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.50, y: 0.03), position: OrganPosition(x: 0.50, y: 0.03),
), ),
OrganModel( OrganModel(
id: 'left_eye', id: 'left_eye',
name: 'Left Eye', name: 'eyes',
description: 'Left Eye',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.43, y: 0.07), position: OrganPosition(x: 0.43, y: 0.07),
), ),
OrganModel( OrganModel(
id: 'right_eye', id: 'right_eye',
name: 'Right Eye', name: 'eyes',
description: 'Right Eye',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.57, y: 0.07), position: OrganPosition(x: 0.57, y: 0.07),
), ),
OrganModel( OrganModel(
id: 'nose_mouth', id: 'nose_mouth',
name: 'Nose/Mouth', name: 'oral_cavity',
description: 'Nose/Mouth',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.50, y: 0.09), position: OrganPosition(x: 0.50, y: 0.09),
), ),
@ -33,7 +37,8 @@ class OrganData {
// ========== NECK ========== // ========== NECK ==========
OrganModel( OrganModel(
id: 'throat', id: 'throat',
name: 'Throat', name: 'neck_or_throat',
description: 'Throat',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.50, y: 0.15), position: OrganPosition(x: 0.50, y: 0.15),
), ),
@ -41,13 +46,15 @@ class OrganData {
// ========== SHOULDERS ========== // ========== SHOULDERS ==========
OrganModel( OrganModel(
id: 'left_shoulder', id: 'left_shoulder',
name: 'Left Shoulder', name: 'upper_arm',
description: 'Left Shoulder',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.22, y: 0.22), position: OrganPosition(x: 0.22, y: 0.22),
), ),
OrganModel( OrganModel(
id: 'right_shoulder', id: 'right_shoulder',
name: 'Right Shoulder', name: 'upper_arm',
description: 'Right Shoulder',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.8, y: 0.22), position: OrganPosition(x: 0.8, y: 0.22),
), ),
@ -55,19 +62,22 @@ class OrganData {
// ========== CHEST ========== // ========== CHEST ==========
OrganModel( OrganModel(
id: 'left_chest', id: 'left_chest',
name: 'Left Chest', name: 'chest',
description: 'Left Chest',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.38, y: 0.22), position: OrganPosition(x: 0.38, y: 0.22),
), ),
OrganModel( OrganModel(
id: 'center_chest', id: 'center_chest',
name: 'Center Chest', name: 'chest',
description: 'Center Chest',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.52, y: 0.25), position: OrganPosition(x: 0.52, y: 0.25),
), ),
OrganModel( OrganModel(
id: 'right_chest', id: 'right_chest',
name: 'Right Chest', name: 'chest',
description: 'Right Chest',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.65, y: 0.22), position: OrganPosition(x: 0.65, y: 0.22),
), ),
@ -75,13 +85,15 @@ class OrganData {
// ========== RIBS ========== // ========== RIBS ==========
OrganModel( OrganModel(
id: 'left_ribs', id: 'left_ribs',
name: 'Left Ribs', name: 'chest',
description: 'Left Ribs',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.38, y: 0.35), position: OrganPosition(x: 0.38, y: 0.35),
), ),
OrganModel( OrganModel(
id: 'right_ribs', id: 'right_ribs',
name: 'Right Ribs', name: 'chest',
description: 'Right Ribs',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.64, y: 0.35), position: OrganPosition(x: 0.64, y: 0.35),
), ),
@ -89,19 +101,22 @@ class OrganData {
// ========== ABDOMEN ========== // ========== ABDOMEN ==========
OrganModel( OrganModel(
id: 'upper_abdomen', id: 'upper_abdomen',
name: 'Upper Abdomen', name: 'upper_abdomen',
description: 'Upper Abdomen',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.51, y: 0.31), position: OrganPosition(x: 0.51, y: 0.31),
), ),
OrganModel( OrganModel(
id: 'navel', id: 'navel',
name: 'Navel', name: 'mid_abdomen',
description: 'Navel',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.51, y: 0.38), position: OrganPosition(x: 0.51, y: 0.38),
), ),
OrganModel( OrganModel(
id: 'lower_abdomen', id: 'lower_abdomen',
name: 'Lower Abdomen', name: 'lower_abdomen',
description: 'Lower Abdomen',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.51, y: 0.44), position: OrganPosition(x: 0.51, y: 0.44),
), ),
@ -109,13 +124,15 @@ class OrganData {
// ========== PELVIS ========== // ========== PELVIS ==========
OrganModel( OrganModel(
id: 'left_groin', id: 'left_groin',
name: 'Left Groin', name: 'sexual_organs',
description: 'Left Groin',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.42, y: 0.49), position: OrganPosition(x: 0.42, y: 0.49),
), ),
OrganModel( OrganModel(
id: 'right_groin', id: 'right_groin',
name: 'Right Groin', name: 'sexual_organs',
description: 'Right Groin',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.59, y: 0.49), position: OrganPosition(x: 0.59, y: 0.49),
), ),
@ -124,19 +141,22 @@ class OrganData {
OrganModel( OrganModel(
id: 'left_elbow', id: 'left_elbow',
name: 'Left Elbow', name: 'elbow',
description: 'Left Elbow',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.21, y: 0.35), position: OrganPosition(x: 0.21, y: 0.35),
), ),
OrganModel( OrganModel(
id: 'left_forearm', id: 'left_forearm',
name: 'Left Forearm', name: 'forearm',
description: 'Left Forearm',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.19, y: 0.43), position: OrganPosition(x: 0.19, y: 0.43),
), ),
OrganModel( OrganModel(
id: 'left_wrist', id: 'left_wrist',
name: 'Left Wrist', name: 'hand',
description: 'Left Wrist',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.14, y: 0.5), position: OrganPosition(x: 0.14, y: 0.5),
), ),
@ -145,19 +165,22 @@ class OrganData {
OrganModel( OrganModel(
id: 'right_elbow', id: 'right_elbow',
name: 'Right Elbow', name: 'elbow',
description: 'Right Elbow',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.79, y: 0.35), position: OrganPosition(x: 0.79, y: 0.35),
), ),
OrganModel( OrganModel(
id: 'right_forearm', id: 'right_forearm',
name: 'Right Forearm', name: 'forearm',
description: 'Right Forearm',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.81, y: 0.43), position: OrganPosition(x: 0.81, y: 0.43),
), ),
OrganModel( OrganModel(
id: 'right_wrist', id: 'right_wrist',
name: 'Right Wrist', name: 'hand',
description: 'Right Wrist',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.85, y: 0.5), position: OrganPosition(x: 0.85, y: 0.5),
), ),
@ -165,25 +188,29 @@ class OrganData {
// ========== LEFT LEG ========== // ========== LEFT LEG ==========
OrganModel( OrganModel(
id: 'left_thigh', id: 'left_thigh',
name: 'Left Thigh', name: 'thigh',
description: 'Left Thigh',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.4, y: 0.60), position: OrganPosition(x: 0.4, y: 0.60),
), ),
OrganModel( OrganModel(
id: 'left_knee', id: 'left_knee',
name: 'Left Knee', name: 'knee',
description: 'Left Knee',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.4, y: 0.72), position: OrganPosition(x: 0.4, y: 0.72),
), ),
OrganModel( OrganModel(
id: 'left_shin', id: 'left_shin',
name: 'Left Shin', name: 'lower_leg',
description: 'Left Shin',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.41, y: 0.82), position: OrganPosition(x: 0.41, y: 0.82),
), ),
OrganModel( OrganModel(
id: 'left_ankle', id: 'left_ankle',
name: 'Left Ankle', name: 'foot',
description: 'Left Ankle',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.43, y: 0.91), position: OrganPosition(x: 0.43, y: 0.91),
), ),
@ -191,25 +218,29 @@ class OrganData {
// ========== RIGHT LEG ========== // ========== RIGHT LEG ==========
OrganModel( OrganModel(
id: 'right_thigh', id: 'right_thigh',
name: 'Right Thigh', name: 'thigh',
description: 'Right Thigh',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.65, y: 0.60), position: OrganPosition(x: 0.65, y: 0.60),
), ),
OrganModel( OrganModel(
id: 'right_knee', id: 'right_knee',
name: 'Right Knee', name: 'knee',
description: 'Right Knee',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.6, y: 0.72), position: OrganPosition(x: 0.6, y: 0.72),
), ),
OrganModel( OrganModel(
id: 'right_shin', id: 'right_shin',
name: 'Right Shin', name: 'lower_leg',
description: 'Right Shin',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.57, y: 0.82), position: OrganPosition(x: 0.57, y: 0.82),
), ),
OrganModel( OrganModel(
id: 'right_ankle', id: 'right_ankle',
name: 'Right Ankle', name: 'foot',
description: 'Right Ankle',
bodyView: BodyView.front, bodyView: BodyView.front,
position: OrganPosition(x: 0.57, y: 0.91), position: OrganPosition(x: 0.57, y: 0.91),
), ),
@ -220,13 +251,15 @@ class OrganData {
// ========== HEAD & NECK ========== // ========== HEAD & NECK ==========
OrganModel( OrganModel(
id: 'back_of_head', id: 'back_of_head',
name: 'Back of Head', name: 'whole_head',
description: 'Back of Head',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.50, y: 0.04), position: OrganPosition(x: 0.50, y: 0.04),
), ),
OrganModel( OrganModel(
id: 'neck_back', id: 'neck_back',
name: 'Neck', name: 'nape_of_neck',
description: 'Neck',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.50, y: 0.14), position: OrganPosition(x: 0.50, y: 0.14),
), ),
@ -234,13 +267,15 @@ class OrganData {
// ========== SHOULDERS ========== // ========== SHOULDERS ==========
OrganModel( OrganModel(
id: 'left_shoulder_back', id: 'left_shoulder_back',
name: 'Left Shoulder', name: 'upper_arm',
description: 'Left Shoulder',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.33, y: 0.19), position: OrganPosition(x: 0.33, y: 0.19),
), ),
OrganModel( OrganModel(
id: 'right_shoulder_back', id: 'right_shoulder_back',
name: 'Right Shoulder', name: 'upper_arm',
description: 'Right Shoulder',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.67, y: 0.19), position: OrganPosition(x: 0.67, y: 0.19),
), ),
@ -248,19 +283,22 @@ class OrganData {
// ========== UPPER BACK ========== // ========== UPPER BACK ==========
OrganModel( OrganModel(
id: 'upper_spine', id: 'upper_spine',
name: 'Upper Spine', name: 'back',
description: 'Upper Spine',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.50, y: 0.23), position: OrganPosition(x: 0.50, y: 0.23),
), ),
OrganModel( OrganModel(
id: 'left_upper_back', id: 'left_upper_back',
name: 'Left Upper Back', name: 'back',
description: 'Left Upper Back',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.38, y: 0.28), position: OrganPosition(x: 0.38, y: 0.28),
), ),
OrganModel( OrganModel(
id: 'right_upper_back', id: 'right_upper_back',
name: 'Right Upper Back', name: 'back',
description: 'Right Upper Back',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.62, y: 0.28), position: OrganPosition(x: 0.62, y: 0.28),
), ),
@ -268,19 +306,22 @@ class OrganData {
// ========== MID BACK ========== // ========== MID BACK ==========
OrganModel( OrganModel(
id: 'mid_spine', id: 'mid_spine',
name: 'Mid Spine', name: 'back',
description: 'Mid Spine',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.50, y: 0.35), position: OrganPosition(x: 0.50, y: 0.35),
), ),
OrganModel( OrganModel(
id: 'left_mid_back', id: 'left_mid_back',
name: 'Left Mid Back', name: 'back',
description: 'Left Mid Back',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.38, y: 0.35), position: OrganPosition(x: 0.38, y: 0.35),
), ),
OrganModel( OrganModel(
id: 'right_mid_back', id: 'right_mid_back',
name: 'Right Mid Back', name: 'back',
description: 'Right Mid Back',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.62, y: 0.35), position: OrganPosition(x: 0.62, y: 0.35),
), ),
@ -288,19 +329,22 @@ class OrganData {
// ========== LOWER BACK ========== // ========== LOWER BACK ==========
OrganModel( OrganModel(
id: 'lower_spine', id: 'lower_spine',
name: 'Lower Back', name: 'lower_back',
description: 'Lower Back',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.50, y: 0.43), position: OrganPosition(x: 0.50, y: 0.43),
), ),
OrganModel( OrganModel(
id: 'left_lower_back', id: 'left_lower_back',
name: 'Left Lower Back', name: 'lower_back',
description: 'Left Lower Back',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.40, y: 0.43), position: OrganPosition(x: 0.40, y: 0.43),
), ),
OrganModel( OrganModel(
id: 'right_lower_back', id: 'right_lower_back',
name: 'Right Lower Back', name: 'lower_back',
description: 'Right Lower Back',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.60, y: 0.43), position: OrganPosition(x: 0.60, y: 0.43),
), ),
@ -308,19 +352,22 @@ class OrganData {
// ========== GLUTES ========== // ========== GLUTES ==========
OrganModel( OrganModel(
id: 'sacrum', id: 'sacrum',
name: 'Sacrum', name: 'lower_back',
description: 'Sacrum',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.50, y: 0.52), position: OrganPosition(x: 0.50, y: 0.52),
), ),
OrganModel( OrganModel(
id: 'left_glute', id: 'left_glute',
name: 'Left Glute', name: 'buttocks',
description: 'Left Glute',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.45, y: 0.56), position: OrganPosition(x: 0.45, y: 0.56),
), ),
OrganModel( OrganModel(
id: 'right_glute', id: 'right_glute',
name: 'Right Glute', name: 'buttocks',
description: 'Right Glute',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.55, y: 0.56), position: OrganPosition(x: 0.55, y: 0.56),
), ),
@ -328,25 +375,29 @@ class OrganData {
// ========== LEGS ========== // ========== LEGS ==========
OrganModel( OrganModel(
id: 'left_hamstring', id: 'left_hamstring',
name: 'Left Hamstring', name: 'thigh',
description: 'Left Hamstring',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.43, y: 0.65), position: OrganPosition(x: 0.43, y: 0.65),
), ),
OrganModel( OrganModel(
id: 'right_hamstring', id: 'right_hamstring',
name: 'Right Hamstring', name: 'thigh',
description: 'Right Hamstring',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.57, y: 0.65), position: OrganPosition(x: 0.57, y: 0.65),
), ),
OrganModel( OrganModel(
id: 'left_calf', id: 'left_calf',
name: 'Left Calf', name: 'lower_leg',
description: 'Left Calf',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.43, y: 0.79), position: OrganPosition(x: 0.43, y: 0.79),
), ),
OrganModel( OrganModel(
id: 'right_calf', id: 'right_calf',
name: 'Right Calf', name: 'lower_leg',
description: 'Right Calf',
bodyView: BodyView.back, bodyView: BodyView.back,
position: OrganPosition(x: 0.57, y: 0.79), position: OrganPosition(x: 0.57, y: 0.79),
), ),

@ -3,12 +3,14 @@ import 'package:hmg_patient_app_new/core/enums.dart';
class OrganModel { class OrganModel {
final String id; final String id;
final String name; final String name;
final String description;
final BodyView bodyView; final BodyView bodyView;
final OrganPosition position; final OrganPosition position;
const OrganModel({ const OrganModel({
required this.id, required this.id,
required this.name, required this.name,
required this.description,
required this.bodyView, required this.bodyView,
required this.position, required this.position,
}); });
@ -16,12 +18,14 @@ class OrganModel {
OrganModel copyWith({ OrganModel copyWith({
String? id, String? id,
String? name, String? name,
String? description,
BodyView? bodyView, BodyView? bodyView,
OrganPosition? position, OrganPosition? position,
}) { }) {
return OrganModel( return OrganModel(
id: id ?? this.id, id: id ?? this.id,
name: name ?? this.name, name: name ?? this.name,
description: description ?? this.description,
bodyView: bodyView ?? this.bodyView, bodyView: bodyView ?? this.bodyView,
position: position ?? this.position, position: position ?? this.position,
); );
@ -37,5 +41,3 @@ class OrganPosition {
required this.y, required this.y,
}); });
} }

@ -0,0 +1,151 @@
class BodySymptomResponseModel {
final DataDetails? dataDetails;
BodySymptomResponseModel({
this.dataDetails,
});
factory BodySymptomResponseModel.fromJson(Map<String, dynamic> json) {
return BodySymptomResponseModel(
dataDetails: json['dataDetails'] != null ? DataDetails.fromJson(json['dataDetails']) : null,
);
}
Map<String, dynamic> toJson() {
return {
'dataDetails': dataDetails?.toJson(),
};
}
}
class DataDetails {
final List<OrganSymptomResult>? result;
final int? id;
final dynamic exception;
final int? status;
final bool? isCanceled;
final bool? isCompleted;
final bool? isCompletedSuccessfully;
final int? creationOptions;
final dynamic asyncState;
final bool? isFaulted;
DataDetails({
this.result,
this.id,
this.exception,
this.status,
this.isCanceled,
this.isCompleted,
this.isCompletedSuccessfully,
this.creationOptions,
this.asyncState,
this.isFaulted,
});
factory DataDetails.fromJson(Map<String, dynamic> json) {
return DataDetails(
result: json['Result'] != null ? (json['Result'] as List).map((item) => OrganSymptomResult.fromJson(item)).toList() : null,
id: json['Id'],
exception: json['Exception'],
status: json['Status'],
isCanceled: json['IsCanceled'],
isCompleted: json['IsCompleted'],
isCompletedSuccessfully: json['IsCompletedSuccessfully'],
creationOptions: json['CreationOptions'],
asyncState: json['AsyncState'],
isFaulted: json['IsFaulted'],
);
}
Map<String, dynamic> toJson() {
return {
'Result': result?.map((item) => item.toJson()).toList(),
'Id': id,
'Exception': exception,
'Status': status,
'IsCanceled': isCanceled,
'IsCompleted': isCompleted,
'IsCompletedSuccessfully': isCompletedSuccessfully,
'CreationOptions': creationOptions,
'AsyncState': asyncState,
'IsFaulted': isFaulted,
};
}
}
class OrganSymptomResult {
final String? name;
final List<BodySymptom>? bodySymptoms;
OrganSymptomResult({
this.name,
this.bodySymptoms,
});
factory OrganSymptomResult.fromJson(Map<String, dynamic> json) {
return OrganSymptomResult(
name: json['name'],
bodySymptoms: json['bodySymptoms'] != null ? (json['bodySymptoms'] as List).map((item) => BodySymptom.fromJson(item)).toList() : null,
);
}
Map<String, dynamic> toJson() {
return {
'name': name,
'bodySymptoms': bodySymptoms?.map((item) => item.toJson()).toList(),
};
}
}
class BodySymptom {
final String? id;
final String? type;
final String? symptomsName;
final String? name;
final String? commonName;
final String? nameAr;
final String? commonNameAr;
BodySymptom({
this.id,
this.type,
this.symptomsName,
this.name,
this.commonName,
this.nameAr,
this.commonNameAr,
});
factory BodySymptom.fromJson(Map<String, dynamic> json) {
return BodySymptom(
id: json['Id'],
type: json['type'],
symptomsName: json['symptoms_name'],
name: json['name'],
commonName: json['common_name'],
nameAr: json['nameAr'],
commonNameAr: json['common_nameAr'],
);
}
Map<String, dynamic> toJson() {
return {
'Id': id,
'type': type,
'symptoms_name': symptomsName,
'name': name,
'common_name': commonName,
'nameAr': nameAr,
'common_nameAr': commonNameAr,
};
}
// Helper method to get display name based on locale
String getDisplayName(bool isArabic) {
if (isArabic) {
return commonNameAr ?? nameAr ?? commonName ?? name ?? '';
}
return commonName ?? name ?? '';
}
}

@ -1,27 +1,64 @@
class SymptomModel { class SymptomModel {
final String id; final String id;
final String name; final String name;
final String organId; final String nameAr;
final String? organId;
const SymptomModel({ const SymptomModel({
required this.id, required this.id,
required this.name, required this.name,
required this.organId, this.nameAr = '',
this.organId,
}); });
SymptomModel copyWith({ SymptomModel copyWith({
String? id, String? id,
String? name, String? name,
String? nameAr,
String? organId, String? organId,
}) { }) {
return SymptomModel( return SymptomModel(
id: id ?? this.id, id: id ?? this.id,
name: name ?? this.name, name: name ?? this.name,
nameAr: nameAr ?? this.nameAr,
organId: organId ?? this.organId, organId: organId ?? this.organId,
); );
} }
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'nameAr': nameAr,
'organId': organId,
};
}
factory SymptomModel.fromJson(Map<String, dynamic> json) {
return SymptomModel(
id: json['id'] ?? '',
name: json['name'] ?? '',
nameAr: json['nameAr'] ?? '',
organId: json['organId'],
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is SymptomModel && other.id == id;
}
@override
int get hashCode => id.hashCode;
@override
String toString() {
return 'SymptomModel(id: $id, name: $name, nameAr: $nameAr, organId: $organId)';
}
} }
/// Class to group symptoms by organ
class OrganSymptomsGroup { class OrganSymptomsGroup {
final String organId; final String organId;
final String organName; final String organName;
@ -35,9 +72,16 @@ class OrganSymptomsGroup {
Set<String>? selectedSymptomIds, Set<String>? selectedSymptomIds,
}) : selectedSymptomIds = selectedSymptomIds ?? {}; }) : selectedSymptomIds = selectedSymptomIds ?? {};
/// Get count of selected symptoms
int get selectedCount => selectedSymptomIds.length;
/// Check if any symptoms are selected
bool get hasSelectedSymptoms => selectedSymptomIds.isNotEmpty; bool get hasSelectedSymptoms => selectedSymptomIds.isNotEmpty;
int get selectedCount => selectedSymptomIds.length; /// Get list of selected symptoms
List<SymptomModel> get selectedSymptoms {
return symptoms.where((symptom) => selectedSymptomIds.contains(symptom.id)).toList();
}
OrganSymptomsGroup copyWith({ OrganSymptomsGroup copyWith({
String? organId, String? organId,
@ -52,4 +96,10 @@ class OrganSymptomsGroup {
selectedSymptomIds: selectedSymptomIds ?? this.selectedSymptomIds, selectedSymptomIds: selectedSymptomIds ?? this.selectedSymptomIds,
); );
} }
@override
String toString() {
return 'OrganSymptomsGroup(organId: $organId, organName: $organName, symptomsCount: ${symptoms.length}, selectedCount: $selectedCount)';
}
} }

@ -0,0 +1,86 @@
import 'dart:convert';
import 'dart:developer';
import 'package:dartz/dartz.dart';
import 'package:hmg_patient_app_new/core/api/api_client.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/resp_models/body_symptom_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'package:http/http.dart' as http;
abstract class SymptomsCheckerRepo {
Future<Either<Failure, GenericApiModel<BodySymptomResponseModel>>> getBodySymptomsByName({
required List<String> organNames,
});
}
class SymptomsCheckerRepoImp implements SymptomsCheckerRepo {
final ApiClient apiClient;
final LoggerService loggerService;
SymptomsCheckerRepoImp({
required this.apiClient,
required this.loggerService,
});
@override
Future<Either<Failure, GenericApiModel<BodySymptomResponseModel>>> getBodySymptomsByName({
required List<String> organNames,
}) async {
try {
// API expects a direct JSON array: ["mid_abdomen", "chest"]
// Not an object like: {"organNames": [...]}
// Since ApiClient.post expects Map<String, dynamic> and encodes it as object,
// we make direct HTTP call here to send array body
final String requestBody = jsonEncode(organNames);
loggerService.logInfo("GetBodySymptomsByName Request: $requestBody");
log("GetBodySymptomsByName Request URL: ${ApiConsts.getBodySymptomsByName}");
log("GetBodySymptomsByName Request Body: $requestBody");
// Make direct HTTP POST request with JSON array body
final response = await http.post(
Uri.parse(ApiConsts.getBodySymptomsByName),
headers: {'Content-Type': 'application/json', 'Accept': 'text/plain'},
body: requestBody,
);
final int statusCode = response.statusCode;
log("GetBodySymptomsByName Response Status: $statusCode");
loggerService.logInfo("GetBodySymptomsByName Response Status: $statusCode");
try {
// Parse the response
final responseBody = jsonDecode(response.body);
loggerService.logInfo("GetBodySymptomsByName API Success: $responseBody");
log("GetBodySymptomsByName Response: $responseBody");
BodySymptomResponseModel bodySymptomResponse = BodySymptomResponseModel.fromJson(responseBody);
GenericApiModel<BodySymptomResponseModel> apiResponse = GenericApiModel<BodySymptomResponseModel>(
messageStatus: 1,
statusCode: statusCode,
errorMessage: null,
data: bodySymptomResponse,
);
return Right(apiResponse);
} catch (e, stackTrace) {
loggerService.logError("Error parsing GetBodySymptomsByName response: $e");
loggerService.logError("StackTrace: $stackTrace");
log("Parse Error: $e");
return Left(DataParsingFailure(e.toString()));
}
} catch (e, stackTrace) {
loggerService.logError("Exception in getBodySymptomsByName: $e");
loggerService.logError("StackTrace: $stackTrace");
log("Exception: $e");
return Left(UnknownFailure(e.toString()));
}
}
}

@ -0,0 +1,326 @@
import 'dart:async';
import 'package:flutter/cupertino.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/resp_models/body_symptom_response_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_repo.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
class SymptomsCheckerViewModel extends ChangeNotifier {
final SymptomsCheckerRepo symptomsCheckerRepo;
final ErrorHandlerService errorHandlerService;
SymptomsCheckerViewModel({
required this.symptomsCheckerRepo,
required this.errorHandlerService,
});
// State variables
bool isBodyHidden = false;
BodyView _currentView = BodyView.front;
final Set<String> _selectedOrganIds = {};
bool _isBottomSheetExpanded = false;
// Tooltip state
String? _tooltipOrganId;
Timer? _tooltipTimer;
// API loading states
bool isBodySymptomsLoading = false;
// API data storage - using API models directly
BodySymptomResponseModel? bodySymptomResponse;
// Selected symptoms tracking (organId -> Set of symptom IDs)
final Map<String, Set<String>> _selectedSymptomsByOrgan = {};
// Getters
bool isPossibleConditionsLoading = false;
BodyView get currentView => _currentView;
Set<String> get selectedOrganIds => _selectedOrganIds;
bool get isBottomSheetExpanded => _isBottomSheetExpanded;
String? get tooltipOrganId => _tooltipOrganId;
/// Get organs for current view
List<OrganModel> get currentOrgans => OrganData.getOrgansForView(_currentView);
/// Get all selected organs from both views
List<OrganModel> get selectedOrgans {
final allOrgans = [
...OrganData.frontViewOrgans,
...OrganData.backViewOrgans,
];
return allOrgans.where((organ) => _selectedOrganIds.contains(organ.id)).toList();
}
/// Check if any organs are selected
bool get hasSelectedOrgans => _selectedOrganIds.isNotEmpty;
/// Get count of selected organs
int get selectedOrgansCount => _selectedOrganIds.length;
/// Get organ symptoms from API response
List<OrganSymptomResult> get organSymptomsResults {
if (bodySymptomResponse?.dataDetails?.result == null) {
return [];
}
return bodySymptomResponse!.dataDetails!.result ?? [];
}
/// Get total selected symptoms count across all organs
int get totalSelectedSymptomsCount {
return _selectedSymptomsByOrgan.values.fold(0, (sum, symptomIds) => sum + symptomIds.length);
}
/// Check if at least one symptom is selected
bool get hasSelectedSymptoms {
return _selectedSymptomsByOrgan.values.any((symptomIds) => symptomIds.isNotEmpty);
}
// Methods
/// Toggle between front and back body view
void toggleView() {
_currentView = _currentView == BodyView.front ? BodyView.back : BodyView.front;
notifyListeners();
}
toggleIsBodyHidden() {
isBodyHidden = !isBodyHidden;
notifyListeners();
}
/// Toggle organ selection (add if not selected, remove if selected)
void toggleOrganSelection(String organId) {
if (_selectedOrganIds.contains(organId)) {
_selectedOrganIds.remove(organId);
} else {
_selectedOrganIds.add(organId);
}
// Show tooltip
_showTooltip(organId);
notifyListeners();
}
/// Show tooltip for an organ
void _showTooltip(String organId) {
// Cancel any existing timer
_tooltipTimer?.cancel();
// Set the tooltip organ
_tooltipOrganId = organId;
notifyListeners();
// Hide tooltip after 2 seconds
_tooltipTimer = Timer(const Duration(seconds: 1), () {
_tooltipOrganId = null;
notifyListeners();
});
}
/// Hide tooltip immediately
void hideTooltip() {
_tooltipTimer?.cancel();
_tooltipOrganId = null;
notifyListeners();
}
/// Remove a specific organ from selection
void removeOrgan(String organId) {
_selectedOrganIds.remove(organId);
notifyListeners();
}
/// Clear all selected organs
void clearAllSelections() {
_selectedOrganIds.clear();
notifyListeners();
}
/// Toggle bottom sheet expanded/collapsed state
void toggleBottomSheet() {
_isBottomSheetExpanded = !_isBottomSheetExpanded;
notifyListeners();
}
/// Set bottom sheet expanded state
void setBottomSheetExpanded(bool isExpanded) {
_isBottomSheetExpanded = isExpanded;
notifyListeners();
}
/// Validate if at least one organ is selected
bool validateSelection() {
return _selectedOrganIds.isNotEmpty;
}
/// Get selected organ IDs as a list
List<String> getSelectedOrganIds() {
return _selectedOrganIds.toList();
}
/// Get selected organ names as a list
List<String> getSelectedOrganNames() {
return selectedOrgans.map((organ) => organ.description).toList();
}
/// Initialize symptoms from API based on selected organs
Future<void> initializeSymptomGroups({
Function()? onSuccess,
Function(String)? onError,
}) async {
if (_selectedOrganIds.isEmpty) {
if (onError != null) {
onError('No organs selected');
}
return;
}
// Get the 'name' field from selected organs to send to API
List<String> organNames = selectedOrgans.map((organ) => organ.name).toList();
// Fetch symptoms from API
await getBodySymptomsByName(
organNames: organNames,
onSuccess: (response) {
// API response is already stored in bodySymptomResponse
if (onSuccess != null) {
onSuccess();
}
},
onError: (error) {
if (onError != null) {
onError(error);
}
},
);
}
/// Toggle symptom selection for a specific organ
void toggleSymptomSelection(String organId, String symptomId) {
if (!_selectedSymptomsByOrgan.containsKey(organId)) {
_selectedSymptomsByOrgan[organId] = {};
}
if (_selectedSymptomsByOrgan[organId]!.contains(symptomId)) {
_selectedSymptomsByOrgan[organId]!.remove(symptomId);
} else {
_selectedSymptomsByOrgan[organId]!.add(symptomId);
}
notifyListeners();
}
/// Check if a symptom is selected
bool isSymptomSelected(String organId, String symptomId) {
return _selectedSymptomsByOrgan[organId]?.contains(symptomId) ?? false;
}
/// Get all selected symptoms across all organs (using API models)
List<BodySymptom> getAllSelectedSymptoms() {
List<BodySymptom> allSymptoms = [];
if (bodySymptomResponse?.dataDetails?.result == null) {
return allSymptoms;
}
for (var organResult in bodySymptomResponse!.dataDetails!.result!) {
// Find matching organ ID
String? matchingOrganId;
for (var organ in selectedOrgans) {
if (organ.name == organResult.name) {
matchingOrganId = organ.id;
break;
}
}
if (matchingOrganId != null && _selectedSymptomsByOrgan.containsKey(matchingOrganId)) {
final selectedIds = _selectedSymptomsByOrgan[matchingOrganId]!;
if (organResult.bodySymptoms != null) {
for (var symptom in organResult.bodySymptoms!) {
if (symptom.id != null && selectedIds.contains(symptom.id)) {
allSymptoms.add(symptom);
}
}
}
}
}
return allSymptoms;
}
/// Clear all symptom selections
void clearAllSymptomSelections() {
_selectedSymptomsByOrgan.clear();
notifyListeners();
}
/// Reset the view model to initial state
void reset() {
_currentView = BodyView.front;
_selectedOrganIds.clear();
_selectedSymptomsByOrgan.clear();
bodySymptomResponse = null;
_isBottomSheetExpanded = false;
_tooltipTimer?.cancel();
_tooltipOrganId = null;
notifyListeners();
}
/// Fetch body symptoms by organ names from API
Future<void> getBodySymptomsByName({
required List<String> organNames,
Function(BodySymptomResponseModel)? onSuccess,
Function(String)? onError,
}) async {
isBodySymptomsLoading = true;
notifyListeners();
final result = await symptomsCheckerRepo.getBodySymptomsByName(
organNames: organNames,
);
result.fold(
// Handle failure
(failure) async {
isBodySymptomsLoading = false;
notifyListeners();
await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.toString());
}
},
// Handle success
(apiResponse) {
isBodySymptomsLoading = false;
if (apiResponse.messageStatus == 1 && apiResponse.data != null) {
bodySymptomResponse = apiResponse.data;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse.data!);
}
} else {
notifyListeners();
if (onError != null) {
onError(apiResponse.errorMessage ?? 'Failed to fetch symptoms');
}
}
},
);
}
@override
void dispose() {
_tooltipTimer?.cancel();
super.dispose();
}
}

@ -28,7 +28,7 @@ import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_mo
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart';
import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart';

@ -193,7 +193,7 @@ class AppointmentCard extends StatelessWidget {
).toShimmer2(isShow: isLoading), ).toShimmer2(isShow: isLoading),
AppCustomChipWidget( AppCustomChipWidget(
isIconPNG: true, isIconPNG: true,
icon: getIt.get<AppState>().getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, icon: getIt.get<AppState>().getAuthenticatedUser()?.gender == 1 ? AppAssets.maleImg : AppAssets.femaleImg,
iconSize: 18.h, iconSize: 18.h,
labelText: isLoading ? 'Cardiology' : "Patient: ${getIt.get<AppState>().getAuthenticatedUser()!.firstName!}", labelText: isLoading ? 'Cardiology' : "Patient: ${getIt.get<AppState>().getAuthenticatedUser()!.firstName!}",
).toShimmer2(isShow: isLoading), ).toShimmer2(isShow: isLoading),

@ -152,7 +152,7 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
children: [ children: [
Image.asset(AppAssets.visa, width: 50.h, height: 50.h), Image.asset(AppAssets.visa, width: 50.h, height: 50.h),
SizedBox(width: 8.h), SizedBox(width: 8.h),
Image.asset(AppAssets.Mastercard, width: 40.h, height: 40.h), Image.asset(AppAssets.mastercard, width: 40.h, height: 40.h),
], ],
), ),
SizedBox(height: 16.h), SizedBox(height: 16.h),
@ -191,7 +191,7 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Image.asset(AppAssets.tamara_en, width: 72.h, height: 25.h), Image.asset(AppAssets.tamaraEng, width: 72.h, height: 25.h),
SizedBox(height: 16.h), SizedBox(height: 16.h),
"Tamara".needTranslation.toText16(isBold: true), "Tamara".needTranslation.toText16(isBold: true),
], ],
@ -244,7 +244,10 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
"Insurance expired or inactive".needTranslation.toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(24.h, 0.h), "Insurance expired or inactive"
.needTranslation
.toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500)
.paddingSymmetrical(24.h, 0.h),
CustomButton( CustomButton(
text: LocaleKeys.updateInsurance.tr(context: context), text: LocaleKeys.updateInsurance.tr(context: context),
onPressed: () { onPressed: () {
@ -274,7 +277,10 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
"Amount before tax".needTranslation.toText14(isBold: true), "Amount before tax".needTranslation.toText14(isBold: true),
Utils.getPaymentAmountWithSymbol(bookAppointmentsVM.patientWalkInAppointmentShareResponseModel!.patientShare!.toString().toText16(isBold: true), AppColors.blackColor, 13, Utils.getPaymentAmountWithSymbol(
bookAppointmentsVM.patientWalkInAppointmentShareResponseModel!.patientShare!.toString().toText16(isBold: true),
AppColors.blackColor,
13,
isSaudiCurrency: true), isSaudiCurrency: true),
], ],
).paddingSymmetrical(24.h, 0.h), ).paddingSymmetrical(24.h, 0.h),
@ -283,7 +289,9 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
children: [ children: [
"VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor), "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor),
Utils.getPaymentAmountWithSymbol( Utils.getPaymentAmountWithSymbol(
bookAppointmentsVM.patientWalkInAppointmentShareResponseModel!.patientTaxAmount!.toString().toText14(isBold: true, color: AppColors.greyTextColor), bookAppointmentsVM.patientWalkInAppointmentShareResponseModel!.patientTaxAmount!
.toString()
.toText14(isBold: true, color: AppColors.greyTextColor),
AppColors.greyTextColor, AppColors.greyTextColor,
13, 13,
isSaudiCurrency: true), isSaudiCurrency: true),
@ -295,7 +303,9 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
children: [ children: [
"".needTranslation.toText14(isBold: true), "".needTranslation.toText14(isBold: true),
Utils.getPaymentAmountWithSymbol( Utils.getPaymentAmountWithSymbol(
bookAppointmentsVM.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!.toString().toText24(isBold: true), AppColors.blackColor, 17, bookAppointmentsVM.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!.toString().toText24(isBold: true),
AppColors.blackColor,
17,
isSaudiCurrency: true), isSaudiCurrency: true),
], ],
).paddingSymmetrical(24.h, 0.h), ).paddingSymmetrical(24.h, 0.h),
@ -374,8 +384,10 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
onSuccess: (apiResponse) async { onSuccess: (apiResponse) async {
if (apiResponse.data["status"].toString().toLowerCase() == "success") { if (apiResponse.data["status"].toString().toLowerCase() == "success") {
tamaraOrderID = apiResponse.data["tamara_order_id"].toString(); tamaraOrderID = apiResponse.data["tamara_order_id"].toString();
await payfortViewModel.updateTamaraRequestStatus(responseMessage: "success", status: "14", clientRequestID: transID, tamaraOrderID: tamaraOrderID); await payfortViewModel.updateTamaraRequestStatus(
await payfortViewModel.markAppointmentAsTamaraPaid(projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, appointmentNo: DateTime.now().millisecondsSinceEpoch); responseMessage: "success", status: "14", clientRequestID: transID, tamaraOrderID: tamaraOrderID);
await payfortViewModel.markAppointmentAsTamaraPaid(
projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, appointmentNo: DateTime.now().millisecondsSinceEpoch);
// await myAppointmentsViewModel.addAdvanceNumberRequest( // await myAppointmentsViewModel.addAdvanceNumberRequest(
// advanceNumber: "Tamara-Advance-0000", // advanceNumber: "Tamara-Advance-0000",
// paymentReference: tamaraOrderID, // paymentReference: tamaraOrderID,
@ -418,7 +430,8 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
// } // }
// }); // });
} else { } else {
await payfortViewModel.updateTamaraRequestStatus(responseMessage: "Failed", status: "00", clientRequestID: transID, tamaraOrderID: tamaraOrderID); await payfortViewModel.updateTamaraRequestStatus(
responseMessage: "Failed", status: "00", clientRequestID: transID, tamaraOrderID: tamaraOrderID);
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight( showCommonBottomSheetWithoutHeight(
context, context,
@ -519,7 +532,13 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
startApplePay() async { startApplePay() async {
showCommonBottomSheet(context, showCommonBottomSheet(context,
child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); child: Utils.getLoadingWidget(),
callBackFunc: (str) {},
title: "",
height: ResponsiveExtension.screenHeight * 0.3,
isCloseButtonVisible: false,
isDismissible: false,
isFullScreen: false);
transID = Utils.getAppointmentTransID( transID = Utils.getAppointmentTransID(
bookAppointmentsViewModel.selectedDoctor.projectID!, bookAppointmentsViewModel.selectedDoctor.projectID!,
bookAppointmentsViewModel.selectedDoctor.clinicID!, bookAppointmentsViewModel.selectedDoctor.clinicID!,
@ -529,7 +548,9 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
ApplePayInsertRequest applePayInsertRequest = ApplePayInsertRequest(); ApplePayInsertRequest applePayInsertRequest = ApplePayInsertRequest();
await payfortViewModel.getPayfortConfigurations( await payfortViewModel.getPayfortConfigurations(
serviceId: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(), projectId: bookAppointmentsViewModel.selectedDoctor.projectID!, integrationId: 2); serviceId: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum(),
projectId: bookAppointmentsViewModel.selectedDoctor.projectID!,
integrationId: 2);
applePayInsertRequest.clientRequestID = transID; applePayInsertRequest.clientRequestID = transID;
applePayInsertRequest.clinicID = bookAppointmentsViewModel.selectedDoctor.clinicID!; applePayInsertRequest.clinicID = bookAppointmentsViewModel.selectedDoctor.clinicID!;
@ -621,7 +642,8 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
checkInType: 2, checkInType: 2,
onSuccess: (apiResponse) { onSuccess: (apiResponse) {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(context, title: "Success".needTranslation, child: Utils.getSuccessWidget(loadingText: apiResponse.data["SuccessMsg"]), callBackFunc: () { showCommonBottomSheetWithoutHeight(context,
title: "Success".needTranslation, child: Utils.getSuccessWidget(loadingText: apiResponse.data["SuccessMsg"]), callBackFunc: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
Navigator.pushAndRemoveUntil( Navigator.pushAndRemoveUntil(
context, context,
@ -633,7 +655,8 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
}, },
onError: (error) { onError: (error) {
LoaderBottomSheet.hideLoader(); LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(context, title: "Error".needTranslation, child: Utils.getErrorWidget(loadingText: error), callBackFunc: () { showCommonBottomSheetWithoutHeight(context, title: "Error".needTranslation, child: Utils.getErrorWidget(loadingText: error),
callBackFunc: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, isFullScreen: false); }, isFullScreen: false);
}, },

@ -9,7 +9,6 @@ import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/int_extensions.dart';
@ -42,14 +41,11 @@ import 'package:hmg_patient_app_new/presentation/home/widgets/welcome_widget.dar
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
import 'package:hmg_patient_app_new/presentation/profile_settings/profile_settings.dart'; import 'package:hmg_patient_app_new/presentation/profile_settings/profile_settings.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_screen.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/countdown_timer.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:hmg_patient_app_new/widgets/routes/spring_page_route_builder.dart'; import 'package:hmg_patient_app_new/widgets/routes/spring_page_route_builder.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -97,374 +93,259 @@ class _LandingPageState extends State<LandingPage> {
if (appState.isAuthenticated) { if (appState.isAuthenticated) {
habibWalletVM.initHabibWalletProvider(); habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount(); habibWalletVM.getPatientBalanceAmount();
immediateLiveCareViewModel.initImmediateLiveCare();
immediateLiveCareViewModel.getPatientLiveCareHistory();
myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false); myAppointmentsViewModel.getPatientAppointments(true, false);
myAppointmentsViewModel.getPatientMyDoctors(); myAppointmentsViewModel.getPatientMyDoctors();
prescriptionsViewModel.initPrescriptionsViewModel(); prescriptionsViewModel.initPrescriptionsViewModel();
insuranceViewModel.initInsuranceProvider(); insuranceViewModel.initInsuranceProvider();
immediateLiveCareViewModel.initImmediateLiveCare();
immediateLiveCareViewModel.getPatientLiveCareHistory();
emergencyServicesViewModel.checkPatientERAdvanceBalance(); emergencyServicesViewModel.checkPatientERAdvanceBalance();
} }
}); });
super.initState(); super.initState();
} }
@override Widget buildOptionsForAuthenticatedUser() {
Widget build(BuildContext context) { return Column(
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false); children: [
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context, listen: false); SizedBox(height: 12.h),
prescriptionsViewModel = Provider.of<PrescriptionsViewModel>(context, listen: false); Row(
insuranceViewModel = Provider.of<InsuranceViewModel>(context, listen: false); mainAxisAlignment: MainAxisAlignment.spaceBetween,
immediateLiveCareViewModel = Provider.of<ImmediateLiveCareViewModel>(context, listen: false); children: [
emergencyServicesViewModel = Provider.of<EmergencyServicesViewModel>(context, listen: false); "Appointments & Visits".toText16(isBold: true),
appState = getIt.get<AppState>(); Row(
return PopScope( children: [
canPop: false, LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor),
child: Scaffold( SizedBox(width: 2.h),
backgroundColor: AppColors.bgScaffoldColor, Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h),
body: SingleChildScrollView( ],
padding: EdgeInsets.only(top: kToolbarHeight + 0.h, bottom: 24), ),
child: Column( ],
spacing: 16.h, ).paddingSymmetrical(24.h, 0.h).onPress(() {
children: [ Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage()));
Row( }),
spacing: 8.h, SizedBox(height: 16.h),
mainAxisAlignment: MainAxisAlignment.spaceBetween, Consumer<MyAppointmentsViewModel>(
children: [ builder: (context, myAppointmentsVM, child) {
appState.isAuthenticated return myAppointmentsVM.isMyAppointmentsLoading
? WelcomeWidget( ? Container(
onTap: () { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
Navigator.of(context).push(springPageRoute(ProfileSettings())); color: AppColors.whiteColor,
}, borderRadius: 24.r,
name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'), hasShadow: true,
imageUrl: appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, ),
).expanded child: AppointmentCard(
: CustomButton( patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(),
text: LocaleKeys.loginOrRegister.tr(context: context), myAppointmentsViewModel: myAppointmentsViewModel,
onPressed: () async { bookAppointmentsViewModel: bookAppointmentsViewModel,
await authVM.onLoginPressed(); isLoading: true,
isFromHomePage: true,
// Navigator.pushReplacementNamed( ),
// // context, ).paddingSymmetrical(24.h, 0.h)
// context, : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty
// AppRoutes.zoomCallPage, ? myAppointmentsVM.patientAppointmentsHistoryList.length == 1
// // arguments: CallArguments(appointmentID, "111", "Patient", "40", "1", true, 1), ? Container(
// arguments: CallArguments("test123", "123", "Patient", "40", "0", true, 1), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
// // arguments: CallArguments("SmallDailyStandup9875", "123", "Patient", "40", "0", false, int.parse(widget.incomingCallData!.appointmentNo!)), color: AppColors.whiteColor,
// ); borderRadius: 24.r,
}, hasShadow: true,
backgroundColor: AppColors.secondaryLightRedColor, ),
borderColor: AppColors.secondaryLightRedColor, child: AppointmentCard(
textColor: AppColors.primaryRedColor, patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList.first,
fontSize: 14.f, myAppointmentsViewModel: myAppointmentsViewModel,
fontWeight: FontWeight.w500, bookAppointmentsViewModel: bookAppointmentsViewModel,
borderRadius: 12.r, isLoading: false,
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0), isFromHomePage: true,
height: 40.h, ),
).paddingSymmetrical(24.h, 0.h)
: Swiper(
itemCount: myAppointmentsVM.isMyAppointmentsLoading
? 3
: myAppointmentsVM.patientAppointmentsHistoryList.length < 3
? myAppointmentsVM.patientAppointmentsHistoryList.length
: 3,
layout: SwiperLayout.STACK,
loop: true,
itemWidth: MediaQuery.of(context).size.width - 48.h,
indicatorLayout: PageIndicatorLayout.COLOR,
axisDirection: AxisDirection.right,
controller: _controller,
itemHeight: 200.h,
pagination: const SwiperPagination(
alignment: Alignment.bottomCenter,
margin: EdgeInsets.only(top: 210 + 8 + 24),
builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor),
),
itemBuilder: (BuildContext context, int index) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: AppointmentCard(
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index],
myAppointmentsViewModel: myAppointmentsViewModel,
bookAppointmentsViewModel: bookAppointmentsViewModel,
isLoading: false,
isFromHomePage: true,
),
);
},
)
: Container(
width: double.infinity,
decoration:
RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
child: Padding(
padding: EdgeInsets.all(12.h),
child: Column(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h),
SizedBox(height: 12.h),
"You do not have any upcoming appointment. Please book an appointment".needTranslation.toText12(isCenter: true),
SizedBox(height: 12.h),
CustomButton(
text: LocaleKeys.bookAppo.tr(context: context),
onPressed: () {
Navigator.of(context).push(CustomPageRoute(page: BookAppointmentPage()));
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
fontSize: 14.f,
fontWeight: FontWeight.w500,
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),
icon: AppAssets.add_icon,
iconColor: AppColors.primaryRedColor,
height: 46.h,
),
],
),
), ),
Row( ).paddingSymmetrical(24.h, 0.h);
mainAxisSize: MainAxisSize.min, },
spacing: 12.h, ),
// Consumer for LiveCare pending request
Consumer<ImmediateLiveCareViewModel>(
builder: (context, immediateLiveCareVM, child) {
return immediateLiveCareVM.patientHasPendingLiveCareRequest
? Column(
children: [ children: [
Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 18.h, width: 18.h).onPress(() { SizedBox(height: 12.h),
Navigator.of(context).push( Container(
CustomPageRoute( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
page: MedicalFilePage(), color: AppColors.whiteColor,
// page: LoginScreen(), borderRadius: 20.r,
hasShadow: true,
side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h),
),
width: double.infinity,
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AppCustomChipWidget(
labelText: immediateLiveCareViewModel.patientLiveCareHistoryList[0].stringCallStatus,
backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20),
textColor: AppColors.alertColor,
),
Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h),
// Lottie.asset(AppAnimations.pending_loading_animation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 40.h, height: 40.h, fit: BoxFit.contain),
],
),
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"You have a pending LiveCare request".needTranslation.toText12(isBold: true),
Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.blackColor,
width: 20.h,
height: 15.h,
fit: BoxFit.contain,
),
],
),
],
), ),
); ),
}), ).paddingSymmetrical(24.h, 0.h).onPress(() {
Utils.buildSvgWithAssets(icon: AppAssets.search_icon, height: 18.h, width: 18.h).onPress(() { Navigator.of(context).push(CustomPageRoute(page: ImmediateLiveCarePendingRequestPage()));
// Navigator.of(context).push(
// CustomPageRoute(
// page: MedicalFilePage(),
// // page: LoginScreen(),
// ),
// );
}),
Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 18.h, width: 18.h).onPress(() {
showCommonBottomSheetWithoutHeight(
context,
title: LocaleKeys.contactUs.tr(),
child: ContactUs(),
callBackFunc: () {},
isFullScreen: false,
);
}), }),
SizedBox(height: 12.h),
], ],
), )
], : SizedBox(height: 12.h);
).paddingSymmetrical(24.h, 0.h), },
appState.isAuthenticated ),
? Column(
children: [
SizedBox(height: 12.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"Appointments & Visits".toText16(isBold: true),
Row(
children: [
LocaleKeys.viewAll.tr(context: context).toText12(color: AppColors.primaryRedColor),
SizedBox(width: 2.h),
Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h),
],
),
],
).paddingSymmetrical(24.h, 0.h).onPress(() {
Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage()));
}),
SizedBox(height: 16.h),
Consumer2<MyAppointmentsViewModel, ImmediateLiveCareViewModel>(
builder: (context, myAppointmentsVM, immediateLiveCareVM, child) {
return myAppointmentsVM.isMyAppointmentsLoading
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: AppointmentCard(
patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(),
myAppointmentsViewModel: myAppointmentsViewModel,
bookAppointmentsViewModel: bookAppointmentsViewModel,
isLoading: true,
isFromHomePage: true,
),
).paddingSymmetrical(24.h, 0.h)
: myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty
? myAppointmentsVM.patientAppointmentsHistoryList.length == 1
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: AppointmentCard(
patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList.first,
myAppointmentsViewModel: myAppointmentsViewModel,
bookAppointmentsViewModel: bookAppointmentsViewModel,
isLoading: false,
isFromHomePage: true,
),
).paddingSymmetrical(24.h, 0.h)
: Swiper(
itemCount: myAppointmentsVM.isMyAppointmentsLoading
? 3
: myAppointmentsVM.patientAppointmentsHistoryList.length < 3
? myAppointmentsVM.patientAppointmentsHistoryList.length
: 3,
layout: SwiperLayout.STACK,
loop: true,
itemWidth: MediaQuery.of(context).size.width - 48.h,
indicatorLayout: PageIndicatorLayout.COLOR,
axisDirection: AxisDirection.right,
controller: _controller,
itemHeight: 255.h,
pagination: SwiperPagination(
alignment: Alignment.bottomCenter,
margin: EdgeInsets.only(top: 240.h + 8 + 24),
builder: DotSwiperPaginationBuilder(color: Color(0xffD9D9D9), activeColor: AppColors.blackBgColor),
),
itemBuilder: (BuildContext context, int index) {
return (immediateLiveCareVM.patientHasPendingLiveCareRequest && index == 0)
? Column(
children: [
SizedBox(height: 12.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.r,
hasShadow: true,
side: BorderSide(color: AppColors.ratingColorYellow, width: 3.h),
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Immediate LiveCare Request".needTranslation.toText16(isBold: true),
SizedBox(height: 10.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
AppCustomChipWidget(
labelText: immediateLiveCareVM.patientLiveCareHistoryList[0].stringCallStatus,
backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20),
textColor: AppColors.alertColor,
),
SizedBox(width: 8.w),
AppCustomChipWidget(
icon: AppAssets.appointment_calendar_icon,
labelText: DateUtil.formatDateToDate(
DateUtil.convertStringToDate(immediateLiveCareVM.patientLiveCareHistoryList[0].arrivalTime), false)),
],
),
Utils.buildSvgWithAssets(icon: AppAssets.waiting_icon, width: 24.h, height: 24.h),
// Lottie.asset(AppAnimations.pending_loading_animation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 80.h, height: 80.h, fit: BoxFit.cover),
],
),
SizedBox(height: 10.h),
"Hala ${appState.getAuthenticatedUser()!.firstName}!!!".needTranslation.toText16(isBold: true),
SizedBox(height: 8.h),
"Your turn is after ${immediateLiveCareVM.patientLiveCareHistoryList[0].patCount} patients.".toText14(isBold: true),
SizedBox(height: 8.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Expected waiting time: ".toText12(isBold: true),
SizedBox(height: 8.h),
ValueListenableBuilder<Duration>(
valueListenable: immediateLiveCareVM.durationNotifier,
builder: (context, duration, child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
buildTime(duration),
],
);
},
),
SizedBox(height: 12.h),
],
),
// CustomButton(
// text: "View Details".needTranslation,
// onPressed: () async {
// Navigator.of(context).push(CustomPageRoute(page: ImmediateLiveCarePendingRequestPage()));
// },
// backgroundColor: Color(0xffFEE9EA),
// borderColor: Color(0xffFEE9EA),
// textColor: Color(0xffED1C2B),
// fontSize: 14.f,
// fontWeight: FontWeight.w500,
// borderRadius: 12.r,
// padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),
// height: 40.h,
// ),
],
),
),
).paddingSymmetrical(0.h, 0.h).onPress(() {
Navigator.of(context).push(CustomPageRoute(page: ImmediateLiveCarePendingRequestPage()));
}),
SizedBox(height: 12.h),
],
)
: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.r,
hasShadow: true,
),
child: AppointmentCard(
patientAppointmentHistoryResponseModel:
myAppointmentsVM.patientAppointmentsHistoryList[immediateLiveCareViewModel.patientHasPendingLiveCareRequest ? --index : index],
myAppointmentsViewModel: myAppointmentsViewModel,
bookAppointmentsViewModel: bookAppointmentsViewModel,
isLoading: false,
isFromHomePage: true,
),
);
},
)
: Container(
width: double.infinity,
decoration: RoundedRectangleBorder()
.toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r, hasShadow: true),
child: Padding(
padding: EdgeInsets.all(12.h),
child: Column(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.home_calendar_icon, width: 32.h, height: 32.h),
SizedBox(height: 12.h),
"You do not have any upcoming appointment. Please book an appointment"
.needTranslation
.toText12(isCenter: true),
SizedBox(height: 12.h),
CustomButton(
text: LocaleKeys.bookAppo.tr(context: context),
onPressed: () {
Navigator.of(context).push(CustomPageRoute(page: BookAppointmentPage()));
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Color(0xffED1C2B),
fontSize: 14.f,
fontWeight: FontWeight.w500,
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),
icon: AppAssets.add_icon,
iconColor: AppColors.primaryRedColor,
height: 46.h,
),
],
),
),
).paddingSymmetrical(24.h, 0.h);
},
),
// Consumer for ER Online Check-In pending request // Consumer for ER Online Check-In pending request
Consumer<EmergencyServicesViewModel>( Consumer<EmergencyServicesViewModel>(
builder: (context, emergencyServicesVM, child) { builder: (context, emergencyServicesVM, child) {
return emergencyServicesVM.patientHasAdvanceERBalance return emergencyServicesVM.patientHasAdvanceERBalance
? Column( ? Column(
children: [ children: [
SizedBox(height: 4.h), SizedBox(height: 4.h),
Container( Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor, color: AppColors.whiteColor,
borderRadius: 20.r, borderRadius: 20.r,
hasShadow: true, hasShadow: true,
side: BorderSide(color: AppColors.primaryRedColor, width: 3.h), side: BorderSide(color: AppColors.primaryRedColor, width: 3.h),
),
width: double.infinity,
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AppCustomChipWidget(
labelText: "ER Online Check-In Request",
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.10),
textColor: AppColors.primaryRedColor,
),
Utils.buildSvgWithAssets(icon: AppAssets.appointment_checkin_icon, width: 24.h, height: 24.h, iconColor: AppColors.primaryRedColor),
],
),
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"You have ER Online Check-In Request".needTranslation.toText12(isBold: true),
Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.blackColor,
width: 20.h,
height: 15.h,
fit: BoxFit.contain,
),
],
),
],
),
),
).paddingSymmetrical(24.h, 0.h).onPress(() {
Navigator.of(context).push(CustomPageRoute(page: ErOnlineCheckinHome()));
// context.read<EmergencyServicesViewModel>().navigateToEROnlineCheckIn();
}),
SizedBox(height: 12.h),
],
)
: SizedBox(height: 12.h);
},
), ),
width: double.infinity,
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AppCustomChipWidget(
labelText: "ER Online Check-In Request",
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.10),
textColor: AppColors.primaryRedColor,
),
Utils.buildSvgWithAssets(
icon: AppAssets.appointment_checkin_icon, width: 24.h, height: 24.h, iconColor: AppColors.primaryRedColor),
],
),
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"You have ER Online Check-In Request".needTranslation.toText12(isBold: true),
Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.blackColor,
width: 20.h,
height: 15.h,
fit: BoxFit.contain,
),
],
),
],
),
),
).paddingSymmetrical(24.h, 0.h).onPress(() {
Navigator.of(context).push(CustomPageRoute(page: ErOnlineCheckinHome()));
// context.read<EmergencyServicesViewModel>().navigateToEROnlineCheckIn();
}),
SizedBox(height: 12.h),
],
)
: SizedBox(height: 12.h);
},
),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -644,7 +525,7 @@ class _LandingPageState extends State<LandingPage> {
Navigator.of(context).push(CustomPageRoute(page: ServicesPage())); Navigator.of(context).push(CustomPageRoute(page: ServicesPage()));
}), }),
], ],
).paddingSymmetrical(24.w, 0.h), ).paddingSymmetrical(24.h, 0.h),
SizedBox( SizedBox(
height: 340.h, height: 340.h,
child: ListView.separated( child: ListView.separated(
@ -691,13 +572,10 @@ class _LandingPageState extends State<LandingPage> {
isDone: isDone, isDone: isDone,
onPressed: () { onPressed: () {
// sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true); // sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true);
authVM.loginWithFingerPrintFace(() async { authVM.loginWithFingerPrintFace(() {
isDone = true; isDone = true;
cacheService.saveBool(key: CacheConst.quickLoginEnabled, value: true); cacheService.saveBool(key: CacheConst.quickLoginEnabled, value: true);
setState(() {}); setState(() {});
await Future.delayed(Duration(milliseconds: 2000)).then((value) {
if (mounted) Navigator.pop(context);
});
}); });
}, },
); );

@ -8,7 +8,7 @@ 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/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_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/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/interactive_body_widget.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/interactive_body_widget.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -267,7 +267,7 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
runSpacing: 8.h, runSpacing: 8.h,
children: viewModel.selectedOrgans.map((organ) { children: viewModel.selectedOrgans.map((organ) {
return AppCustomChipWidget( return AppCustomChipWidget(
labelText: organ.name, labelText: organ.description,
backgroundColor: AppColors.secondaryLightRedColor, backgroundColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor, textColor: AppColors.primaryRedColor,
deleteIcon: AppAssets.cancel, deleteIcon: AppAssets.cancel,

@ -9,7 +9,7 @@ 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/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/conditions_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/models/conditions_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/symptoms_checker_view_model.dart'; import 'package:hmg_patient_app_new/features/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/condition_card.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/condition_card.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';

@ -0,0 +1,291 @@
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/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/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.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 RiskFactorsScreen extends StatefulWidget {
const RiskFactorsScreen({super.key});
@override
State<RiskFactorsScreen> createState() => _RiskFactorsScreenState();
}
class _RiskFactorsScreenState extends State<RiskFactorsScreen> {
@override
void initState() {
super.initState();
// Initialize symptom groups based on selected organs
WidgetsBinding.instance.addPostFrameCallback((_) {
final viewModel = context.read<SymptomsCheckerViewModel>();
viewModel.initializeSymptomGroups();
});
}
void _onOptionSelected(int optionIndex) {}
void _onNextPressed(SymptomsCheckerViewModel viewModel) {
if (viewModel.hasSelectedSymptoms) {
// Navigate to triage screen
context.navigateWithName(AppRoutes.suggestionsScreen);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Please select at least one option before proceeding'.needTranslation),
backgroundColor: AppColors.errorColor,
),
);
}
}
void _onPreviousPressed() {
context.pop();
}
_buildConfirmationBottomSheet({required BuildContext context, required VoidCallback onConfirm}) {
return showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: context),
context,
child: Utils.getWarningWidget(
loadingText: "Are you sure you want to restart the organ selection?".needTranslation,
isShowActionButtons: true,
onCancelTap: () => Navigator.pop(context),
onConfirmTap: () => onConfirm(),
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
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.borderGrayColor, width: 1.w),
),
child: selected ? Icon(Icons.check, size: 16.f, color: AppColors.whiteColor) : null,
),
SizedBox(width: 12.w),
Expanded(
child: Text(
optionText,
style: TextStyle(fontSize: 14.f, color: AppColors.textColor, fontWeight: FontWeight.w500),
),
),
],
),
),
);
}
Widget buildFactorsList() {
return AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
transitionBuilder: (Widget child, Animation<double> animation) {
final offsetAnimation = Tween<Offset>(
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(
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: [
...List.generate(4, (index) {
return _buildOptionItem(index, false, "currentQuestion.options[index].text");
}),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Consumer<SymptomsCheckerViewModel>(
builder: (context, viewModel, _) {
return Column(
children: [
Expanded(
child: CollapsingListView(
title: "Risks".needTranslation,
onLeadingTapped: () => _buildConfirmationBottomSheet(
context: context,
onConfirm: () => {
context.pop(),
context.pop(),
}),
child: _buildEmptyState(),
// child: viewModel.organSymptomsGroups.isEmpty
// ? _buildEmptyState()
// : Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// SizedBox(height: 16.h),
// ...viewModel.organSymptomsGroups.map((group) {
// return Padding(
// padding: EdgeInsets.only(bottom: 16.h),
// child: 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: [
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Expanded(
// child: Text(
// 'Possible symptoms related to "${group.organName}"',
// style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, color: AppColors.textColor),
// ),
// ),
// ],
// ),
// SizedBox(height: 24.h),
// Wrap(
// runSpacing: 12.h,
// spacing: 8.w,
// children: group.symptoms.map((symptom) {
// bool isSelected = viewModel.isSymptomSelected(group.organId, symptom.id);
// return GestureDetector(
// onTap: () => viewModel.toggleSymptomSelection(group.organId, symptom.id),
// child: CustomSelectableChip(
// label: symptom.name,
// selected: isSelected,
// activeColor: AppColors.primaryRedBorderColor,
// activeTextColor: AppColors.primaryRedBorderColor,
// inactiveBorderColor: AppColors.bottomNAVBorder,
// inactiveTextColor: AppColors.textColor,
// ),
// );
// }).toList(),
// ),
// ],
// ),
// ),
// );
// }),
// ],
// ),
),
),
_buildStickyBottomCard(context, viewModel),
],
);
},
),
);
}
Widget _buildEmptyState() {
return Center(
child: Padding(
padding: EdgeInsets.all(24.h),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.info_outline, size: 64.h, color: AppColors.greyTextColor),
SizedBox(height: 16.h),
Text(
'No organs selected'.needTranslation,
style: TextStyle(
fontSize: 18.f,
fontWeight: FontWeight.w600,
color: AppColors.textColor,
),
),
SizedBox(height: 8.h),
Text(
'Please go back and select organs first'.needTranslation,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.f,
color: AppColors.greyTextColor,
),
),
],
),
),
);
}
Widget _buildStickyBottomCard(BuildContext context, SymptomsCheckerViewModel viewModel) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 16.h),
Row(
children: [
Expanded(
child: CustomButton(
text: "Previous".needTranslation,
onPressed: _onPreviousPressed,
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,
onPressed: () => _onNextPressed(viewModel),
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 16.f,
),
),
],
),
SizedBox(height: 24.h),
],
).paddingSymmetrical(24.w, 0),
);
}
}

@ -0,0 +1,292 @@
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/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/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.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 SuggestionsScreen extends StatefulWidget {
const SuggestionsScreen({super.key});
@override
State<SuggestionsScreen> createState() => _SuggestionsScreenState();
}
class _SuggestionsScreenState extends State<SuggestionsScreen> {
@override
void initState() {
super.initState();
// Initialize symptom groups based on selected organs
WidgetsBinding.instance.addPostFrameCallback((_) {
final viewModel = context.read<SymptomsCheckerViewModel>();
viewModel.initializeSymptomGroups();
});
}
void _onOptionSelected(int optionIndex) {}
void _onNextPressed(SymptomsCheckerViewModel viewModel) {
if (viewModel.hasSelectedSymptoms) {
// Navigate to triage screen
context.navigateWithName(AppRoutes.triageScreen);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Please select at least one option before proceeding'.needTranslation),
backgroundColor: AppColors.errorColor,
),
);
}
}
void _onPreviousPressed() {
context.pop();
}
_buildConfirmationBottomSheet({required BuildContext context, required VoidCallback onConfirm}) {
return showCommonBottomSheetWithoutHeight(
title: LocaleKeys.notice.tr(context: context),
context,
child: Utils.getWarningWidget(
loadingText: "Are you sure you want to restart the organ selection?".needTranslation,
isShowActionButtons: true,
onCancelTap: () => Navigator.pop(context),
onConfirmTap: () => onConfirm(),
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
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.borderGrayColor, width: 1.w),
),
child: selected ? Icon(Icons.check, size: 16.f, color: AppColors.whiteColor) : null,
),
SizedBox(width: 12.w),
Expanded(
child: Text(
optionText,
style: TextStyle(fontSize: 14.f, color: AppColors.textColor, fontWeight: FontWeight.w500),
),
),
],
),
),
);
}
Widget buildFactorsList() {
return AnimatedSwitcher(
duration: const Duration(milliseconds: 400),
transitionBuilder: (Widget child, Animation<double> animation) {
final offsetAnimation = Tween<Offset>(
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(
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: [
...List.generate(4, (index) {
return _buildOptionItem(index, false, "currentQuestion.options[index].text");
}),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Consumer<SymptomsCheckerViewModel>(
builder: (context, viewModel, _) {
return Column(
children: [
Expanded(
child: CollapsingListView(
title: "Suggestions".needTranslation,
onLeadingTapped: () => _buildConfirmationBottomSheet(
context: context,
onConfirm: () => {
context.pop(),
context.pop(),
}),
child: _buildEmptyState(),
// child: viewModel.organSymptomsGroups.isEmpty
// ? _buildEmptyState()
// : Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// SizedBox(height: 16.h),
// ...viewModel.organSymptomsGroups.map((group) {
// return Padding(
// padding: EdgeInsets.only(bottom: 16.h),
// child: 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: [
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Expanded(
// child: Text(
// 'Possible symptoms related to "${group.organName}"',
// style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, color: AppColors.textColor),
// ),
// ),
// ],
// ),
// SizedBox(height: 24.h),
// Wrap(
// runSpacing: 12.h,
// spacing: 8.w,
// children: group.symptoms.map((symptom) {
// bool isSelected = viewModel.isSymptomSelected(group.organId, symptom.id);
// return GestureDetector(
// onTap: () => viewModel.toggleSymptomSelection(group.organId, symptom.id),
// child: CustomSelectableChip(
// label: symptom.name,
// selected: isSelected,
// activeColor: AppColors.primaryRedBorderColor,
// activeTextColor: AppColors.primaryRedBorderColor,
// inactiveBorderColor: AppColors.bottomNAVBorder,
// inactiveTextColor: AppColors.textColor,
// ),
// );
// }).toList(),
// ),
// ],
// ),
// ),
// );
// }),
// ],
// ),
),
),
_buildStickyBottomCard(context, viewModel),
],
);
},
),
);
}
Widget _buildEmptyState() {
return Center(
child: Padding(
padding: EdgeInsets.all(24.h),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.info_outline, size: 64.h, color: AppColors.greyTextColor),
SizedBox(height: 16.h),
Text(
'No organs selected'.needTranslation,
style: TextStyle(
fontSize: 18.f,
fontWeight: FontWeight.w600,
color: AppColors.textColor,
),
),
SizedBox(height: 8.h),
Text(
'Please go back and select organs first'.needTranslation,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.f,
color: AppColors.greyTextColor,
),
),
],
),
),
);
}
Widget _buildStickyBottomCard(BuildContext context, SymptomsCheckerViewModel viewModel) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 16.h),
Row(
children: [
Expanded(
child: CustomButton(
text: "Previous".needTranslation,
onPressed: _onPreviousPressed,
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,
onPressed: () => _onNextPressed(viewModel),
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 16.f,
),
),
],
),
SizedBox(height: 24.h),
],
).paddingSymmetrical(24.w, 0),
);
}
}

@ -1,240 +0,0 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/data/symptoms_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/symptom_model.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/organ_mapping_data.dart';
class SymptomsCheckerViewModel extends ChangeNotifier {
// State variables
bool isBodyHidden = false;
BodyView _currentView = BodyView.front;
final Set<String> _selectedOrganIds = {};
bool _isBottomSheetExpanded = false;
// Symptom selection state
final Map<String, OrganSymptomsGroup> _organSymptomsGroups = {};
// Tooltip state
String? _tooltipOrganId;
Timer? _tooltipTimer;
// Getters
bool isPossibleConditionsLoading = false;
BodyView get currentView => _currentView;
Set<String> get selectedOrganIds => _selectedOrganIds;
bool get isBottomSheetExpanded => _isBottomSheetExpanded;
String? get tooltipOrganId => _tooltipOrganId;
/// Get organs for current view
List<OrganModel> get currentOrgans => OrganData.getOrgansForView(_currentView);
/// Get all selected organs from both views
List<OrganModel> get selectedOrgans {
final allOrgans = [
...OrganData.frontViewOrgans,
...OrganData.backViewOrgans,
];
return allOrgans.where((organ) => _selectedOrganIds.contains(organ.id)).toList();
}
/// Check if any organs are selected
bool get hasSelectedOrgans => _selectedOrganIds.isNotEmpty;
/// Get count of selected organs
int get selectedOrgansCount => _selectedOrganIds.length;
/// Get organ symptoms groups for selected organs
List<OrganSymptomsGroup> get organSymptomsGroups {
return _organSymptomsGroups.values.toList();
}
/// Get total selected symptoms count across all organs
int get totalSelectedSymptomsCount {
return _organSymptomsGroups.values.fold(0, (sum, group) => sum + group.selectedCount);
}
/// Check if at least one symptom is selected
bool get hasSelectedSymptoms {
return _organSymptomsGroups.values.any((group) => group.hasSelectedSymptoms);
}
// Methods
/// Toggle between front and back body view
void toggleView() {
_currentView = _currentView == BodyView.front ? BodyView.back : BodyView.front;
notifyListeners();
}
toggleIsBodyHidden() {
isBodyHidden = !isBodyHidden;
notifyListeners();
}
/// Toggle organ selection (add if not selected, remove if selected)
void toggleOrganSelection(String organId) {
if (_selectedOrganIds.contains(organId)) {
_selectedOrganIds.remove(organId);
} else {
_selectedOrganIds.add(organId);
}
// Show tooltip
_showTooltip(organId);
notifyListeners();
}
/// Show tooltip for an organ
void _showTooltip(String organId) {
// Cancel any existing timer
_tooltipTimer?.cancel();
// Set the tooltip organ
_tooltipOrganId = organId;
notifyListeners();
// Hide tooltip after 2 seconds
_tooltipTimer = Timer(const Duration(seconds: 1), () {
_tooltipOrganId = null;
notifyListeners();
});
}
/// Hide tooltip immediately
void hideTooltip() {
_tooltipTimer?.cancel();
_tooltipOrganId = null;
notifyListeners();
}
/// Remove a specific organ from selection
void removeOrgan(String organId) {
_selectedOrganIds.remove(organId);
notifyListeners();
}
/// Clear all selected organs
void clearAllSelections() {
_selectedOrganIds.clear();
notifyListeners();
}
/// Toggle bottom sheet expanded/collapsed state
void toggleBottomSheet() {
_isBottomSheetExpanded = !_isBottomSheetExpanded;
notifyListeners();
}
/// Set bottom sheet expanded state
void setBottomSheetExpanded(bool isExpanded) {
_isBottomSheetExpanded = isExpanded;
notifyListeners();
}
/// Validate if at least one organ is selected
bool validateSelection() {
return _selectedOrganIds.isNotEmpty;
}
/// Get selected organ IDs as a list
List<String> getSelectedOrganIds() {
return _selectedOrganIds.toList();
}
/// Get selected organ names as a list
List<String> getSelectedOrganNames() {
return selectedOrgans.map((organ) => organ.name).toList();
}
/// Initialize symptom groups based on selected organs
void initializeSymptomGroups() {
_organSymptomsGroups.clear();
for (String organId in _selectedOrganIds) {
List<SymptomModel> symptoms = SymptomsMappingData.getSymptomsForOrgan(organId);
if (symptoms.isNotEmpty) {
// Find organ name from selectedOrgans
String organName = selectedOrgans
.firstWhere((organ) => organ.id == organId, orElse: () => OrganModel(id: organId, name: organId, bodyView: BodyView.front, position: OrganPosition(x: 0, y: 0)))
.name;
_organSymptomsGroups[organId] = OrganSymptomsGroup(
organId: organId,
organName: organName,
symptoms: symptoms,
);
}
}
notifyListeners();
}
/// Toggle symptom selection for a specific organ
void toggleSymptomSelection(String organId, String symptomId) {
if (_organSymptomsGroups.containsKey(organId)) {
final group = _organSymptomsGroups[organId]!;
final selectedIds = Set<String>.from(group.selectedSymptomIds);
if (selectedIds.contains(symptomId)) {
selectedIds.remove(symptomId);
} else {
selectedIds.add(symptomId);
}
_organSymptomsGroups[organId] = group.copyWith(selectedSymptomIds: selectedIds);
notifyListeners();
}
}
/// Check if a symptom is selected
bool isSymptomSelected(String organId, String symptomId) {
return _organSymptomsGroups[organId]?.selectedSymptomIds.contains(symptomId) ?? false;
}
/// Get all selected symptoms across all organs
List<SymptomModel> getAllSelectedSymptoms() {
List<SymptomModel> allSymptoms = [];
for (var group in _organSymptomsGroups.values) {
for (var symptom in group.symptoms) {
if (group.selectedSymptomIds.contains(symptom.id)) {
allSymptoms.add(symptom);
}
}
}
return allSymptoms;
}
/// Clear all symptom selections
void clearAllSymptomSelections() {
for (var organId in _organSymptomsGroups.keys) {
_organSymptomsGroups[organId] = _organSymptomsGroups[organId]!.copyWith(selectedSymptomIds: {});
}
notifyListeners();
}
/// Reset the view model to initial state
void reset() {
_currentView = BodyView.front;
_selectedOrganIds.clear();
_organSymptomsGroups.clear();
_isBottomSheetExpanded = false;
_tooltipTimer?.cancel();
_tooltipOrganId = null;
notifyListeners();
}
@override
void dispose() {
_tooltipTimer?.cancel();
super.dispose();
}
}

@ -5,9 +5,9 @@ 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/route_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_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/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/symptom_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/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/symptoms_checker_view_model.dart';
import 'package:hmg_patient_app_new/theme/colors.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/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -36,7 +36,7 @@ class _SymptomsSelectorScreenState extends State<SymptomsSelectorScreen> {
void _onNextPressed(SymptomsCheckerViewModel viewModel) { void _onNextPressed(SymptomsCheckerViewModel viewModel) {
if (viewModel.hasSelectedSymptoms) { if (viewModel.hasSelectedSymptoms) {
// Navigate to triage screen // Navigate to triage screen
context.navigateWithName(AppRoutes.triageScreen); context.navigateWithName(AppRoutes.riskFactorsScreen);
} else { } else {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
@ -84,23 +84,47 @@ class _SymptomsSelectorScreenState extends State<SymptomsSelectorScreen> {
context.pop(), context.pop(),
context.pop(), context.pop(),
}), }),
child: viewModel.organSymptomsGroups.isEmpty child: viewModel.isBodySymptomsLoading
? _buildEmptyState() ? _buildLoadingShimmer()
: Column( : viewModel.organSymptomsResults.isEmpty
crossAxisAlignment: CrossAxisAlignment.start, ? _buildEmptyState()
children: [ : Column(
SizedBox(height: 16.h), crossAxisAlignment: CrossAxisAlignment.start,
...viewModel.organSymptomsGroups.map((group) { children: [
return Padding( SizedBox(height: 16.h),
padding: EdgeInsets.only(bottom: 16.h), ...viewModel.organSymptomsResults.map((organResult) {
child: _buildSymptomsSelectionCard(viewModel, group), // Find matching organ ID from selected organs
); String? organId;
}), String? organName;
], for (var organ in viewModel.selectedOrgans) {
), if (organ.name == organResult.name) {
organId = organ.id;
organName = organ.description;
break;
}
}
if (organId != null && organResult.bodySymptoms != null) {
return Padding(
padding: EdgeInsets.only(bottom: 16.h),
child: _buildSymptomsSelectionCard(
viewModel,
organId,
organName ?? organResult.name ?? '',
organResult.bodySymptoms!,
),
);
}
return SizedBox.shrink();
}),
],
),
), ),
), ),
_buildStickyBottomCard(context, viewModel), if (!viewModel.isBodySymptomsLoading) ...[
_buildStickyBottomCard(context, viewModel),
],
], ],
); );
}, },
@ -108,7 +132,12 @@ class _SymptomsSelectorScreenState extends State<SymptomsSelectorScreen> {
); );
} }
Widget _buildSymptomsSelectionCard(SymptomsCheckerViewModel viewModel, OrganSymptomsGroup group) { Widget _buildSymptomsSelectionCard(
SymptomsCheckerViewModel viewModel,
String organId,
String organName,
List<BodySymptom> symptoms,
) {
return Container( return Container(
width: double.infinity, width: double.infinity,
margin: EdgeInsets.symmetric(horizontal: 24.w), margin: EdgeInsets.symmetric(horizontal: 24.w),
@ -122,7 +151,7 @@ class _SymptomsSelectorScreenState extends State<SymptomsSelectorScreen> {
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
'Possible symptoms related to "${group.organName}"', 'Possible symptoms related to "$organName"',
style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, color: AppColors.textColor), style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, color: AppColors.textColor),
), ),
), ),
@ -132,12 +161,14 @@ class _SymptomsSelectorScreenState extends State<SymptomsSelectorScreen> {
Wrap( Wrap(
runSpacing: 12.h, runSpacing: 12.h,
spacing: 8.w, spacing: 8.w,
children: group.symptoms.map((symptom) { children: symptoms.map((symptom) {
bool isSelected = viewModel.isSymptomSelected(group.organId, symptom.id); final symptomId = symptom.id ?? '';
final symptomLabel = symptom.getDisplayName(false); // Pass isArabic from AppState if needed
bool isSelected = viewModel.isSymptomSelected(organId, symptomId);
return GestureDetector( return GestureDetector(
onTap: () => viewModel.toggleSymptomSelection(group.organId, symptom.id), onTap: () => viewModel.toggleSymptomSelection(organId, symptomId),
child: CustomSelectableChip( child: CustomSelectableChip(
label: symptom.name, label: symptomLabel,
selected: isSelected, selected: isSelected,
activeColor: AppColors.primaryRedBorderColor, activeColor: AppColors.primaryRedBorderColor,
activeTextColor: AppColors.primaryRedBorderColor, activeTextColor: AppColors.primaryRedBorderColor,
@ -152,6 +183,64 @@ class _SymptomsSelectorScreenState extends State<SymptomsSelectorScreen> {
); );
} }
Widget _buildLoadingShimmer() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
// Create 2-3 shimmer cards
...List.generate(3, (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(
'Loading..',
style: TextStyle(fontSize: 14.f, color: AppColors.textColor),
),
).toShimmer2(isShow: true, radius: 24.r);
}),
),
],
),
);
}
Widget _buildEmptyState() { Widget _buildEmptyState() {
return Center( return Center(
child: Padding( child: Padding(

@ -118,7 +118,7 @@ class _InteractiveBodyWidgetState extends State<InteractiveBodyWidget> {
(organ) { (organ) {
final centerX = organ.position.x * imageConstraints.maxWidth; final centerX = organ.position.x * imageConstraints.maxWidth;
final centerY = organ.position.y * imageConstraints.maxHeight; final centerY = organ.position.y * imageConstraints.maxHeight;
return OrganTooltipWidget(organName: organ.name, position: Offset(centerX, centerY)); return OrganTooltipWidget(organName: organ.description, position: Offset(centerX, centerY));
}, },
), ),
], ],

@ -114,7 +114,7 @@ class _SelectedOrgansSectionState extends State<SelectedOrgansSection> {
runSpacing: 8.h, runSpacing: 8.h,
children: widget.selectedOrgans.map((organ) { children: widget.selectedOrgans.map((organ) {
return AppCustomChipWidget( return AppCustomChipWidget(
labelText: organ.name, labelText: organ.description,
backgroundColor: AppColors.secondaryLightRedColor, backgroundColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor, textColor: AppColors.primaryRedColor,
deleteIcon: AppAssets.cross_circle, deleteIcon: AppAssets.cross_circle,

@ -9,6 +9,8 @@ import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/organ_selector_screen.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/organ_selector_screen.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/possible_conditions_screen.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/possible_conditions_screen.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/risk_factors_screen.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/suggestions_screen.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/symptoms_selector_screen.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/symptoms_selector_screen.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/triage_screen.dart'; import 'package:hmg_patient_app_new/presentation/symptoms_checker/triage_screen.dart';
import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_screen.dart'; import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_screen.dart';
@ -29,6 +31,8 @@ class AppRoutes {
// Symptoms Checker // Symptoms Checker
static const String organSelectorPage = '/organSelectorPage'; static const String organSelectorPage = '/organSelectorPage';
static const String symptomsCheckerScreen = '/symptomsCheckerScreen'; static const String symptomsCheckerScreen = '/symptomsCheckerScreen';
static const String suggestionsScreen = '/suggestionsScreen';
static const String riskFactorsScreen = '/riskFactorsScreen';
static const String possibleConditionsScreen = '/possibleConditionsScreen'; static const String possibleConditionsScreen = '/possibleConditionsScreen';
static const String triageScreen = '/triageProgressScreen'; static const String triageScreen = '/triageProgressScreen';
@ -39,13 +43,14 @@ class AppRoutes {
register: (context) => RegisterNew(), register: (context) => RegisterNew(),
registerStepTwo: (context) => RegisterNewStep2(), registerStepTwo: (context) => RegisterNewStep2(),
medicalFilePage: (context) => MedicalFilePage(), medicalFilePage: (context) => MedicalFilePage(),
zoomCallPage: (context) => CallScreen(),
eReferralPage: (context) => NewReferralPage(), eReferralPage: (context) => NewReferralPage(),
comprehensiveCheckupPage: (context) => ComprehensiveCheckupPage(), comprehensiveCheckupPage: (context) => ComprehensiveCheckupPage(),
homeHealthCarePage: (context) => HhcProceduresPage(), homeHealthCarePage: (context) => HhcProceduresPage(),
zoomCallPage: (context) => CallScreen()
homeHealthCarePage: (context) => HhcProceduresPage(),
organSelectorPage: (context) => OrganSelectorPage(), organSelectorPage: (context) => OrganSelectorPage(),
symptomsCheckerScreen: (context) => SymptomsSelectorScreen(), symptomsCheckerScreen: (context) => SymptomsSelectorScreen(),
riskFactorsScreen: (context) => RiskFactorsScreen(),
suggestionsScreen: (context) => SuggestionsScreen(),
possibleConditionsScreen: (context) => PossibleConditionsScreen(), possibleConditionsScreen: (context) => PossibleConditionsScreen(),
triageScreen: (context) => TriageScreen() triageScreen: (context) => TriageScreen()
}; };

Loading…
Cancel
Save