Integrated the API for Symptoms Checker

pull/113/head
faizatflutter 1 month 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_TAMARA_OPT = '/api/orders/paymentoptions/tamara';
// 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 = 'http://10.201.204.103/';
// 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
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";
//E_Referral Services
// Encillary Orders
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 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 applePayMerchantId = "merchant.com.hmgwebservices";
@ -844,6 +845,12 @@ class ApiConsts {
static final String updateHHCOrder = 'api/hhc/update';
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
@ -854,7 +861,6 @@ class ApiConsts {
static final createEReferral = "Services/Patients.svc/REST/CreateEReferral";
static final getEReferrals = "Services/Patients.svc/REST/GetEReferrals";
// ************ static values for Api ****************
static final double appVersionID = 50.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_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_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_view_model.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/radiology/radiology_repo.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_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/cache_service.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
@ -94,6 +96,9 @@ class AppDependencies {
final sharedPreferences = await SharedPreferences.getInstance();
getIt.registerLazySingleton<CacheService>(() => CacheServiceImp(sharedPreferences: sharedPreferences, loggerService: getIt()));
getIt.registerLazySingleton<ApiClient>(() => ApiClientImp(appState: getIt()));
getIt.registerLazySingleton<LocalAuthService>(
() => LocalAuthService(loggerService: getIt<LoggerService>(), localAuth: getIt<LocalAuthentication>()),
);
// Repositories
getIt.registerLazySingleton<CommonRepo>(() => CommonRepoImp(loggerService: getIt()));
@ -105,9 +110,6 @@ class AppDependencies {
getIt.registerLazySingleton<PrescriptionsRepo>(() => PrescriptionsRepoImp(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<LocalAuthService>(
() => LocalAuthService(loggerService: getIt<LoggerService>(), localAuth: getIt<LocalAuthentication>()),
);
getIt.registerLazySingleton<HabibWalletRepo>(() => HabibWalletRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<MedicalFileRepo>(() => MedicalFileRepoImp(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<ContactUsRepo>(() => ContactUsRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<HmgServicesRepo>(() => HmgServicesRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
getIt.registerLazySingleton<SymptomsCheckerRepo>(() => SymptomsCheckerRepoImp(loggerService: getIt<LoggerService>(), apiClient: getIt()));
// ViewModels
// Global/shared VMs LazySingleton
@ -126,7 +129,8 @@ class AppDependencies {
() => 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()));
@ -216,7 +220,7 @@ class AppDependencies {
() => TodoSectionViewModel(todoSectionRepo: getIt(), errorHandlerService: getIt()),
);
getIt.registerLazySingleton<SymptomsCheckerViewModel>(() => SymptomsCheckerViewModel());
getIt.registerLazySingleton<SymptomsCheckerViewModel>(() => SymptomsCheckerViewModel(errorHandlerService: getIt(), symptomsCheckerRepo: getIt()));
getIt.registerLazySingleton<HmgServicesViewModel>(
() => 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) {
await getCalenders();
}
@ -38,12 +44,14 @@ class CalenderUtilsNew {
// String eventId = await getEventIdIfEventExist(title!.split("#").last);
// if (eventId.isEmpty) {
eventResult = await DeviceCalendar.instance.createEvent(
calendarId: writableCalendars!.id,
title: title,
description: description,
startDate: scheduleDateTime!,
endDate: scheduleDateTime!.add(Duration(minutes: 30)),
reminderMinutes: reminderMinutes);
calendarId: writableCalendars!.id,
title: title,
description: description,
startDate: scheduleDateTime!,
endDate: scheduleDateTime!.add(Duration(minutes: 30)),
// reminderMinutes: reminderMinutes, // TODO : NEED TO CONFIRM THIS FROM TAHA
);
return eventResult.isNotEmpty;
// }

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

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

@ -3,12 +3,14 @@ import 'package:hmg_patient_app_new/core/enums.dart';
class OrganModel {
final String id;
final String name;
final String description;
final BodyView bodyView;
final OrganPosition position;
const OrganModel({
required this.id,
required this.name,
required this.description,
required this.bodyView,
required this.position,
});
@ -16,12 +18,14 @@ class OrganModel {
OrganModel copyWith({
String? id,
String? name,
String? description,
BodyView? bodyView,
OrganPosition? position,
}) {
return OrganModel(
id: id ?? this.id,
name: name ?? this.name,
description: description ?? this.description,
bodyView: bodyView ?? this.bodyView,
position: position ?? this.position,
);
@ -37,5 +41,3 @@ class OrganPosition {
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 {
final String id;
final String name;
final String organId;
final String nameAr;
final String? organId;
const SymptomModel({
required this.id,
required this.name,
required this.organId,
this.nameAr = '',
this.organId,
});
SymptomModel copyWith({
String? id,
String? name,
String? nameAr,
String? organId,
}) {
return SymptomModel(
id: id ?? this.id,
name: name ?? this.name,
nameAr: nameAr ?? this.nameAr,
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 {
final String organId;
final String organName;
@ -35,9 +72,16 @@ class OrganSymptomsGroup {
Set<String>? selectedSymptomIds,
}) : selectedSymptomIds = selectedSymptomIds ?? {};
/// Get count of selected symptoms
int get selectedCount => selectedSymptomIds.length;
/// Check if any symptoms are selected
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({
String? organId,
@ -52,4 +96,10 @@ class OrganSymptomsGroup {
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/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/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/services/logger_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';

@ -193,7 +193,7 @@ class AppointmentCard extends StatelessWidget {
).toShimmer2(isShow: isLoading),
AppCustomChipWidget(
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,
labelText: isLoading ? 'Cardiology' : "Patient: ${getIt.get<AppState>().getAuthenticatedUser()!.firstName!}",
).toShimmer2(isShow: isLoading),

@ -152,7 +152,7 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
children: [
Image.asset(AppAssets.visa, width: 50.h, height: 50.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),
@ -191,7 +191,7 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
Column(
crossAxisAlignment: CrossAxisAlignment.start,
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),
"Tamara".needTranslation.toText16(isBold: true),
],
@ -244,7 +244,10 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
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(
text: LocaleKeys.updateInsurance.tr(context: context),
onPressed: () {
@ -274,7 +277,10 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"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),
],
).paddingSymmetrical(24.h, 0.h),
@ -283,7 +289,9 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
children: [
"VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor),
Utils.getPaymentAmountWithSymbol(
bookAppointmentsVM.patientWalkInAppointmentShareResponseModel!.patientTaxAmount!.toString().toText14(isBold: true, color: AppColors.greyTextColor),
bookAppointmentsVM.patientWalkInAppointmentShareResponseModel!.patientTaxAmount!
.toString()
.toText14(isBold: true, color: AppColors.greyTextColor),
AppColors.greyTextColor,
13,
isSaudiCurrency: true),
@ -295,7 +303,9 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
children: [
"".needTranslation.toText14(isBold: true),
Utils.getPaymentAmountWithSymbol(
bookAppointmentsVM.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!.toString().toText24(isBold: true), AppColors.blackColor, 17,
bookAppointmentsVM.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!.toString().toText24(isBold: true),
AppColors.blackColor,
17,
isSaudiCurrency: true),
],
).paddingSymmetrical(24.h, 0.h),
@ -374,8 +384,10 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
onSuccess: (apiResponse) async {
if (apiResponse.data["status"].toString().toLowerCase() == "success") {
tamaraOrderID = apiResponse.data["tamara_order_id"].toString();
await payfortViewModel.updateTamaraRequestStatus(responseMessage: "success", status: "14", clientRequestID: transID, tamaraOrderID: tamaraOrderID);
await payfortViewModel.markAppointmentAsTamaraPaid(projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, appointmentNo: DateTime.now().millisecondsSinceEpoch);
await payfortViewModel.updateTamaraRequestStatus(
responseMessage: "success", status: "14", clientRequestID: transID, tamaraOrderID: tamaraOrderID);
await payfortViewModel.markAppointmentAsTamaraPaid(
projectID: bookAppointmentsViewModel.selectedDoctor.projectID!, appointmentNo: DateTime.now().millisecondsSinceEpoch);
// await myAppointmentsViewModel.addAdvanceNumberRequest(
// advanceNumber: "Tamara-Advance-0000",
// paymentReference: tamaraOrderID,
@ -418,7 +430,8 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
// }
// });
} 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();
showCommonBottomSheetWithoutHeight(
context,
@ -519,7 +532,13 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
startApplePay() async {
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(
bookAppointmentsViewModel.selectedDoctor.projectID!,
bookAppointmentsViewModel.selectedDoctor.clinicID!,
@ -529,7 +548,9 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
ApplePayInsertRequest applePayInsertRequest = ApplePayInsertRequest();
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.clinicID = bookAppointmentsViewModel.selectedDoctor.clinicID!;
@ -621,7 +642,8 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
checkInType: 2,
onSuccess: (apiResponse) {
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.pushAndRemoveUntil(
context,
@ -633,7 +655,8 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
},
onError: (error) {
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();
}, 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/cache_consts.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/utils.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/profile_settings/profile_settings.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/theme/colors.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/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/spring_page_route_builder.dart';
import 'package:provider/provider.dart';
@ -97,374 +93,259 @@ class _LandingPageState extends State<LandingPage> {
if (appState.isAuthenticated) {
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
immediateLiveCareViewModel.initImmediateLiveCare();
immediateLiveCareViewModel.getPatientLiveCareHistory();
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
myAppointmentsViewModel.getPatientMyDoctors();
prescriptionsViewModel.initPrescriptionsViewModel();
insuranceViewModel.initInsuranceProvider();
immediateLiveCareViewModel.initImmediateLiveCare();
immediateLiveCareViewModel.getPatientLiveCareHistory();
emergencyServicesViewModel.checkPatientERAdvanceBalance();
}
});
super.initState();
}
@override
Widget build(BuildContext context) {
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context, listen: false);
prescriptionsViewModel = Provider.of<PrescriptionsViewModel>(context, listen: false);
insuranceViewModel = Provider.of<InsuranceViewModel>(context, listen: false);
immediateLiveCareViewModel = Provider.of<ImmediateLiveCareViewModel>(context, listen: false);
emergencyServicesViewModel = Provider.of<EmergencyServicesViewModel>(context, listen: false);
appState = getIt.get<AppState>();
return PopScope(
canPop: false,
child: Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: SingleChildScrollView(
padding: EdgeInsets.only(top: kToolbarHeight + 0.h, bottom: 24),
child: Column(
spacing: 16.h,
children: [
Row(
spacing: 8.h,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
appState.isAuthenticated
? WelcomeWidget(
onTap: () {
Navigator.of(context).push(springPageRoute(ProfileSettings()));
},
name: ('${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}'),
imageUrl: appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg,
).expanded
: CustomButton(
text: LocaleKeys.loginOrRegister.tr(context: context),
onPressed: () async {
await authVM.onLoginPressed();
// Navigator.pushReplacementNamed(
// // context,
// context,
// AppRoutes.zoomCallPage,
// // arguments: CallArguments(appointmentID, "111", "Patient", "40", "1", true, 1),
// arguments: CallArguments("test123", "123", "Patient", "40", "0", true, 1),
// // arguments: CallArguments("SmallDailyStandup9875", "123", "Patient", "40", "0", false, int.parse(widget.incomingCallData!.appointmentNo!)),
// );
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
padding: EdgeInsets.fromLTRB(10.h, 0, 10.h, 0),
height: 40.h,
Widget buildOptionsForAuthenticatedUser() {
return 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),
Consumer<MyAppointmentsViewModel>(
builder: (context, myAppointmentsVM, 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: 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(
mainAxisSize: MainAxisSize.min,
spacing: 12.h,
).paddingSymmetrical(24.h, 0.h);
},
),
// Consumer for LiveCare pending request
Consumer<ImmediateLiveCareViewModel>(
builder: (context, immediateLiveCareVM, child) {
return immediateLiveCareVM.patientHasPendingLiveCareRequest
? Column(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.bell, height: 18.h, width: 18.h).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: MedicalFilePage(),
// page: LoginScreen(),
SizedBox(height: 12.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
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,
),
],
),
],
),
);
}),
Utils.buildSvgWithAssets(icon: AppAssets.search_icon, height: 18.h, width: 18.h).onPress(() {
// 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,
);
),
).paddingSymmetrical(24.h, 0.h).onPress(() {
Navigator.of(context).push(CustomPageRoute(page: ImmediateLiveCarePendingRequestPage()));
}),
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);
},
),
)
: SizedBox(height: 12.h);
},
),
// Consumer for ER Online Check-In pending request
Consumer<EmergencyServicesViewModel>(
builder: (context, emergencyServicesVM, child) {
return emergencyServicesVM.patientHasAdvanceERBalance
? Column(
children: [
SizedBox(height: 4.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.r,
hasShadow: true,
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);
},
// Consumer for ER Online Check-In pending request
Consumer<EmergencyServicesViewModel>(
builder: (context, emergencyServicesVM, child) {
return emergencyServicesVM.patientHasAdvanceERBalance
? Column(
children: [
SizedBox(height: 4.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.r,
hasShadow: true,
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);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -644,7 +525,7 @@ class _LandingPageState extends State<LandingPage> {
Navigator.of(context).push(CustomPageRoute(page: ServicesPage()));
}),
],
).paddingSymmetrical(24.w, 0.h),
).paddingSymmetrical(24.h, 0.h),
SizedBox(
height: 340.h,
child: ListView.separated(
@ -691,13 +572,10 @@ class _LandingPageState extends State<LandingPage> {
isDone: isDone,
onPressed: () {
// sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true);
authVM.loginWithFingerPrintFace(() async {
authVM.loginWithFingerPrintFace(() {
isDone = true;
cacheService.saveBool(key: CacheConst.quickLoginEnabled, value: true);
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/string_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/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -267,7 +267,7 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
runSpacing: 8.h,
children: viewModel.selectedOrgans.map((organ) {
return AppCustomChipWidget(
labelText: organ.name,
labelText: organ.description,
backgroundColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
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/features/symptoms_checker/models/conditions_model.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/services/navigation_service.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/string_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/presentation/symptoms_checker/symptoms_checker_view_model.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';
@ -36,7 +36,7 @@ class _SymptomsSelectorScreenState extends State<SymptomsSelectorScreen> {
void _onNextPressed(SymptomsCheckerViewModel viewModel) {
if (viewModel.hasSelectedSymptoms) {
// Navigate to triage screen
context.navigateWithName(AppRoutes.triageScreen);
context.navigateWithName(AppRoutes.riskFactorsScreen);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
@ -84,23 +84,47 @@ class _SymptomsSelectorScreenState extends State<SymptomsSelectorScreen> {
context.pop(),
context.pop(),
}),
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: _buildSymptomsSelectionCard(viewModel, group),
);
}),
],
),
child: viewModel.isBodySymptomsLoading
? _buildLoadingShimmer()
: viewModel.organSymptomsResults.isEmpty
? _buildEmptyState()
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
...viewModel.organSymptomsResults.map((organResult) {
// 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(
width: double.infinity,
margin: EdgeInsets.symmetric(horizontal: 24.w),
@ -122,7 +151,7 @@ class _SymptomsSelectorScreenState extends State<SymptomsSelectorScreen> {
children: [
Expanded(
child: Text(
'Possible symptoms related to "${group.organName}"',
'Possible symptoms related to "$organName"',
style: TextStyle(fontSize: 18.f, fontWeight: FontWeight.w600, color: AppColors.textColor),
),
),
@ -132,12 +161,14 @@ class _SymptomsSelectorScreenState extends State<SymptomsSelectorScreen> {
Wrap(
runSpacing: 12.h,
spacing: 8.w,
children: group.symptoms.map((symptom) {
bool isSelected = viewModel.isSymptomSelected(group.organId, symptom.id);
children: symptoms.map((symptom) {
final symptomId = symptom.id ?? '';
final symptomLabel = symptom.getDisplayName(false); // Pass isArabic from AppState if needed
bool isSelected = viewModel.isSymptomSelected(organId, symptomId);
return GestureDetector(
onTap: () => viewModel.toggleSymptomSelection(group.organId, symptom.id),
onTap: () => viewModel.toggleSymptomSelection(organId, symptomId),
child: CustomSelectableChip(
label: symptom.name,
label: symptomLabel,
selected: isSelected,
activeColor: 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() {
return Center(
child: Padding(

@ -118,7 +118,7 @@ class _InteractiveBodyWidgetState extends State<InteractiveBodyWidget> {
(organ) {
final centerX = organ.position.x * imageConstraints.maxWidth;
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,
children: widget.selectedOrgans.map((organ) {
return AppCustomChipWidget(
labelText: organ.name,
labelText: organ.description,
backgroundColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
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/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/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/triage_screen.dart';
import 'package:hmg_patient_app_new/presentation/tele_consultation/zoom/call_screen.dart';
@ -29,6 +31,8 @@ class AppRoutes {
// Symptoms Checker
static const String organSelectorPage = '/organSelectorPage';
static const String symptomsCheckerScreen = '/symptomsCheckerScreen';
static const String suggestionsScreen = '/suggestionsScreen';
static const String riskFactorsScreen = '/riskFactorsScreen';
static const String possibleConditionsScreen = '/possibleConditionsScreen';
static const String triageScreen = '/triageProgressScreen';
@ -39,13 +43,14 @@ class AppRoutes {
register: (context) => RegisterNew(),
registerStepTwo: (context) => RegisterNewStep2(),
medicalFilePage: (context) => MedicalFilePage(),
zoomCallPage: (context) => CallScreen(),
eReferralPage: (context) => NewReferralPage(),
comprehensiveCheckupPage: (context) => ComprehensiveCheckupPage(),
homeHealthCarePage: (context) => HhcProceduresPage(),
zoomCallPage: (context) => CallScreen()
homeHealthCarePage: (context) => HhcProceduresPage(),
organSelectorPage: (context) => OrganSelectorPage(),
symptomsCheckerScreen: (context) => SymptomsSelectorScreen(),
riskFactorsScreen: (context) => RiskFactorsScreen(),
suggestionsScreen: (context) => SuggestionsScreen(),
possibleConditionsScreen: (context) => PossibleConditionsScreen(),
triageScreen: (context) => TriageScreen()
};

Loading…
Cancel
Save