Merge pull request 'feature/search_by_region' (#38) from feature/search_by_region into master

Reviewed-on: #38
pull/43/head
Haroon6138 1 month ago
commit 6a4650d658

@ -0,0 +1,5 @@
<svg width="10" height="13" viewBox="0 0 10 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 2.85449C3.79188 2.85449 2.8125 3.83387 2.8125 5.04199C2.8125 6.25011 3.79188 7.22949 5 7.22949C6.20812 7.22949 7.1875 6.25011 7.1875 5.04199C7.1875 3.83387 6.20812 2.85449 5 2.85449ZM3.6875 5.04199C3.6875 4.31712 4.27513 3.72949 5 3.72949C5.72487 3.72949 6.3125 4.31712 6.3125 5.04199C6.3125 5.76687 5.72487 6.35449 5 6.35449C4.27513 6.35449 3.6875 5.76687 3.6875 5.04199Z" fill="#ED1C2B"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 0.229492C2.38798 0.229492 0.1875 2.42783 0.1875 5.09268C0.1875 7.8228 2.43772 9.64709 4.31579 10.8329L4.32334 10.8377L4.33107 10.8422C4.5346 10.9593 4.76514 11.0212 5 11.0212C5.23486 11.0212 5.4654 10.9593 5.66893 10.8422L5.67567 10.8383L5.68227 10.8342C7.56769 9.65737 9.8125 7.81358 9.8125 5.09268C9.8125 2.42783 7.61202 0.229492 5 0.229492ZM1.0625 5.09268C1.0625 2.90553 2.87676 1.10449 5 1.10449C7.12324 1.10449 8.9375 2.90553 8.9375 5.09268C8.9375 7.29808 7.10717 8.91207 5.22662 10.0871C5.15694 10.1259 5.079 10.1462 5 10.1462C4.92132 10.1462 4.84369 10.1261 4.77422 10.0876C2.89372 8.89884 1.0625 7.30546 1.0625 5.09268Z" fill="#ED1C2B"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 10.7295C1.73936 10.7295 1.93382 10.9217 1.93745 11.1602C1.94159 11.1712 1.96197 11.2114 2.04585 11.2775C2.16816 11.3739 2.37227 11.4798 2.66348 11.5769C3.24132 11.7695 4.06744 11.8962 5 11.8962C5.93256 11.8962 6.75868 11.7695 7.33652 11.5769C7.62773 11.4798 7.83184 11.3739 7.95415 11.2775C8.03803 11.2114 8.05841 11.1712 8.06255 11.1602C8.06618 10.9217 8.26064 10.7295 8.5 10.7295C8.74162 10.7295 8.9375 10.9254 8.9375 11.167C8.9375 11.5196 8.72273 11.7859 8.49575 11.9647C8.26385 12.1475 7.95539 12.2929 7.61322 12.407C6.92432 12.6366 6.00044 12.7712 5 12.7712C3.99956 12.7712 3.07568 12.6366 2.38678 12.407C2.04461 12.2929 1.73615 12.1475 1.50425 11.9647C1.27727 11.7859 1.0625 11.5196 1.0625 11.167C1.0625 10.9254 1.25838 10.7295 1.5 10.7295ZM8.06349 11.1572C8.06349 11.1571 8.06349 11.1572 8.06349 11.1572V11.1572Z" fill="#ED1C2B"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@ -0,0 +1,5 @@
<svg width="14" height="13" viewBox="0 0 14 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.47635 0.357633C1.3055 0.186779 1.02849 0.186779 0.857633 0.357633C0.686779 0.528487 0.686779 0.805497 0.857633 0.976351L2.46119 2.57991C1.74908 3.50857 1.31283 4.60696 1.31283 5.87371C1.31283 7.46181 1.98475 8.7894 2.92684 9.88512C3.86488 10.9761 5.08602 11.856 6.2293 12.5637L6.23681 12.5684L6.24451 12.5727C6.47506 12.7029 6.73552 12.7712 7.00033 12.7712C7.26514 12.7712 7.52559 12.7029 7.75614 12.5727L7.76285 12.5689L7.76943 12.5649C8.63521 12.0351 9.66148 11.3872 10.4965 10.6152L12.5243 12.643C12.6952 12.8139 12.9722 12.8139 13.143 12.643C13.3139 12.4722 13.3139 12.1952 13.143 12.0243L8.48292 7.3642C8.47847 7.35955 8.47393 7.35501 8.46931 7.35059L5.56673 4.44802C5.5623 4.44339 5.55776 4.43884 5.5531 4.43438L1.47635 0.357633ZM4.72223 4.84095L3.08613 3.20485C2.51802 3.9791 2.18783 4.86436 2.18783 5.87371C2.18783 7.19521 2.74204 8.32806 3.59032 9.31467C4.44051 10.3035 5.57042 11.1262 6.68141 11.8145C6.77877 11.868 6.88851 11.8962 7.00033 11.8962C7.11248 11.8962 7.22255 11.8678 7.32013 11.814C8.18224 11.2864 9.12483 10.6862 9.87729 9.99601L8.07636 8.19508C7.73263 8.32898 7.33404 8.39615 7.00033 8.39615C5.63112 8.39615 4.52116 7.28619 4.52116 5.91698C4.52116 5.58326 4.58833 5.18468 4.72223 4.84095ZM7.36809 7.48681L5.4305 5.54922C5.40846 5.67294 5.39616 5.79917 5.39616 5.91698C5.39616 6.80294 6.11437 7.52115 7.00033 7.52115C7.11814 7.52115 7.24437 7.50884 7.36809 7.48681Z" fill="#2E3039"/>
<path d="M7.0004 1.10449C6.13419 1.10449 5.31049 1.34359 4.59613 1.76103C4.38751 1.88294 4.11957 1.81265 3.99766 1.60403C3.87575 1.39541 3.94605 1.12747 4.15466 1.00556C4.99638 0.513701 5.97157 0.229492 7.0004 0.229492C10.0778 0.229492 12.6879 2.77256 12.6879 5.87369C12.6879 6.94853 12.3781 7.91037 11.8817 8.76226C11.76 8.97103 11.4922 9.04165 11.2834 8.92C11.0746 8.79834 11.004 8.53049 11.1257 8.32172C11.5556 7.58384 11.8129 6.77241 11.8129 5.87369C11.8129 3.26657 9.60535 1.10449 7.0004 1.10449Z" fill="#2E3039"/>
<path d="M7.00038 4.31283C6.92215 4.31283 6.84547 4.31839 6.77066 4.32909C6.53147 4.36331 6.30984 4.19714 6.27562 3.95795C6.24141 3.71876 6.40758 3.49712 6.64677 3.46291C6.76248 3.44636 6.88056 3.43783 7.00038 3.43783C8.36959 3.43783 9.47955 4.54779 9.47955 5.91699C9.47955 6.03681 9.47102 6.15489 9.45446 6.27061C9.42025 6.5098 9.19861 6.67596 8.95942 6.64175C8.72023 6.60754 8.55407 6.3859 8.58828 6.14671C8.59898 6.07191 8.60455 5.99523 8.60455 5.91699C8.60455 5.03103 7.88634 4.31283 7.00038 4.31283Z" fill="#2E3039"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

@ -818,4 +818,11 @@
"ready": "جاهز", "ready": "جاهز",
"enterValidNationalId": "الرجاء إدخال رقم الهوية الوطنية أو رقم الملف الصحيح", "enterValidNationalId": "الرجاء إدخال رقم الهوية الوطنية أو رقم الملف الصحيح",
"enterValidPhoneNumber": "الرجاء إدخال رقم هاتف صالح" "enterValidPhoneNumber": "الرجاء إدخال رقم هاتف صالح"
"medicalCentersWithCount": "{count} مراكز طبية",
"medicalCenters": "مراكز طبية",
"hospitalsWithCount": "{count} مستشفيات",
"selectRegion": "اختر المنطقة",
"selectFacility": "اختر المرافق",
"selectFacilitiesSubTitle": "يرجى اختيار المرفق للموعد",
"selectHospitalSubTitle": "يرجى اختيار المستشفى للموعد"
} }

@ -813,5 +813,12 @@
"enterValidNationalId": "Please enter a valid national ID or file number", "enterValidNationalId": "Please enter a valid national ID or file number",
"enterValidPhoneNumber": "Please enter a valid phone number", "enterValidPhoneNumber": "Please enter a valid phone number",
"ready": "Ready", "ready": "Ready",
"news": "News" "medicalCentersWithCount" : "{count} Medical Centers",
"medicalCenters" : " Medical Centers",
"hospitalsWithCount" : "{count} Hospitals",
"selectRegion": "Select Region",
"selectFacility": "Select Facilities",
"selectFacilitiesSubTitle": "Please select the facility for the appointment",
"selectHospitalSubTitle": "Please select the hospital for the appointment"
} }

@ -56,7 +56,7 @@ var RC_BASE_URL = 'https://rc.hmg.com/';
var PING_SERVICE = 'Services/Weather.svc/REST/CheckConnectivity'; var PING_SERVICE = 'Services/Weather.svc/REST/CheckConnectivity';
var GET_PROJECT = 'Services/Lists.svc/REST/GetProject'; var GET_PROJECT_LIST = 'Services/Lists.svc/REST/GetProject';
///Geofencing ///Geofencing
var GET_GEO_ZONES = 'Services/Patients.svc/REST/GeoF_GetAllPoints'; var GET_GEO_ZONES = 'Services/Patients.svc/REST/GeoF_GetAllPoints';
@ -217,9 +217,6 @@ var GET_CLINICS_LIST_WRT_HOSPITAL_URL = "Services/Lists.svc/REST/GetClinicFromDo
//URL to get active appointment list //URL to get active appointment list
var GET_ACTIVE_APPOINTMENTS_LIST_URL = "Services/Doctors.svc/Rest/Dr_GetAppointmentActiveNumber"; var GET_ACTIVE_APPOINTMENTS_LIST_URL = "Services/Doctors.svc/Rest/Dr_GetAppointmentActiveNumber";
//URL to get projects list
var GET_PROJECTS_LIST = 'Services/Lists.svc/REST/GetProject';
//URL to get doctors list //URL to get doctors list
var GET_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/SearchDoctorsByTime"; var GET_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/SearchDoctorsByTime";

@ -108,6 +108,8 @@ class AppAssets {
static const String search_by_clinic_icon = '$svgBasePath/search_by_clinic_icon.svg'; static const String search_by_clinic_icon = '$svgBasePath/search_by_clinic_icon.svg';
static const String search_by_doctor_icon = '$svgBasePath/search_by_doctor_icon.svg'; static const String search_by_doctor_icon = '$svgBasePath/search_by_doctor_icon.svg';
static const String search_by_region_icon = '$svgBasePath/search_by_region_icon.svg'; static const String search_by_region_icon = '$svgBasePath/search_by_region_icon.svg';
static const String location_red = '$svgBasePath/location_red.svg';
static const String location_unavailable = '$svgBasePath/location_unavailable.svg';
static const String livecare_clinic_icon = '$svgBasePath/livecare_clinic_icon.svg'; static const String livecare_clinic_icon = '$svgBasePath/livecare_clinic_icon.svg';
static const String immediate_service_icon = '$svgBasePath/immediate_service_icon.svg'; static const String immediate_service_icon = '$svgBasePath/immediate_service_icon.svg';
static const String no_visit_icon = '$svgBasePath/no_visit_icon.svg'; static const String no_visit_icon = '$svgBasePath/no_visit_icon.svg';

@ -132,4 +132,6 @@ class AppState {
set setUserRegistrationPayload(RegistrationDataModelPayload value) { set setUserRegistrationPayload(RegistrationDataModelPayload value) {
_userRegistrationPayload = value; _userRegistrationPayload = value;
} }
} }

@ -128,6 +128,7 @@ class AppDependencies {
() => MyAppointmentsViewModel( () => MyAppointmentsViewModel(
myAppointmentsRepo: getIt(), myAppointmentsRepo: getIt(),
errorHandlerService: getIt(), errorHandlerService: getIt(),
appState: getIt()
), ),
); );

@ -29,7 +29,7 @@ enum CountryEnum { saudiArabia, unitedArabEmirates }
enum CalenderEnum { gregorian, hijri } enum CalenderEnum { gregorian, hijri }
enum SelectionTypeEnum { dropdown, calendar } enum SelectionTypeEnum { dropdown, calendar, search }
enum GenderTypeEnum { male, female } enum GenderTypeEnum { male, female }

@ -0,0 +1,207 @@
import 'dart:math';
import 'package:hmg_patient_app_new/core/cache_consts.dart' show CacheConst;
import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils;
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart' show RegionList, PatientDoctorAppointmentList, DoctorList, PatientDoctorAppointmentListByRegion;
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart' show HospitalsModel;
class DoctorMapper{
static Future<RegionList> getMappedDoctor(List<DoctorList> doctorList,
{bool isArabic = false}) async {
RegionList regionList = RegionList();
final lat = await Utils.getNumFromPrefs(CacheConst.userLat);
final long = await Utils.getNumFromPrefs(CacheConst.userLat);
for (var element in doctorList) {
String? region = element.getRegionName(isArabic);
if (region == null) continue;
var regionDoctorList = regionList.registeredDoctorMap?.putIfAbsent(region, () => PatientDoctorAppointmentListByRegion());
List<PatientDoctorAppointmentList>? targetList = element.isHMC == true
? regionDoctorList?.hmcDoctorList
: regionDoctorList?.hmgDoctorList;
var doctorByHospital = targetList
?.where((clinic) =>
clinic.filterName ==
element.getProjectCompleteNameWithLocale(isArabic: isArabic))
.toList() ??
[];
if (doctorByHospital.isNotEmpty) {
doctorByHospital.first.patientDoctorAppointmentList?.add(element);
} else {
var newAppointment = PatientDoctorAppointmentList(
filterName:
element.getProjectCompleteNameWithLocale(isArabic: isArabic),
distanceInKMs: element.projectDistanceInKiloMeters.toString(),
projectTopName: element.projectTopName,
projectBottomName: element.projectBottomName,
patientDoctorAppointment: element,
isHMC: element.isHMC
);
if(element.projectDistanceInKiloMeters!= null ){
if(regionDoctorList!.distance>element.projectDistanceInKiloMeters){
regionDoctorList.distance = element.projectDistanceInKiloMeters;
}
if (element.isHMC == true &&
element.projectDistanceInKiloMeters <
regionDoctorList.hmcDistance) {
regionDoctorList.hmcDistance = element.projectDistanceInKiloMeters;
} else if (element.projectDistanceInKiloMeters <
regionDoctorList.hmgDistance) {
regionDoctorList.hmgDistance = element.projectDistanceInKiloMeters;
}
}else
if (lat != 0&&
long != 0 && element.latitude != null && element.longitude != null) {
double distance = calculateDistance(lat.toDouble(), long.toDouble(), double.parse(element.latitude!), double.parse(element.longitude!));
if(distance<0){
distance *= -1;
}
if(regionDoctorList!.distance>distance){
regionDoctorList.distance = distance;
}
if (element.isHMC == true &&
element.projectDistanceInKiloMeters <
regionDoctorList.hmcDistance) {
regionDoctorList.hmcDistance = element.projectDistanceInKiloMeters;
} else if (element.projectDistanceInKiloMeters <
regionDoctorList.hmgDistance) {
regionDoctorList.hmgDistance = element.projectDistanceInKiloMeters;
}
}
targetList?.add(newAppointment);
}
regionDoctorList?.hmcSize = regionDoctorList.hmcDoctorList?.length ?? 0;
regionDoctorList?.hmgSize = regionDoctorList.hmgDoctorList?.length ?? 0;
regionList.registeredDoctorMap?[region] = regionDoctorList;
}
return regionList;
}
static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
var pi = 3.142;
const double R = 6371;
double dLat = (lat2 - lat1) * pi / 180;
double dLon = (lon2 - lon1) * pi / 180;
double a = sin(dLat / 2) * sin(dLat / 2) +
cos(lat1 * pi / 180) * cos(lat2 * pi / 180) * sin(dLon / 2) * sin(dLon / 2);
double c = 2 * atan2(sqrt(a), sqrt(1 - a));
return R * c;
}
static Future<RegionList> sortList(bool isGPSEnabled, RegionList unsorted, ) async {
if(isGPSEnabled){
if(unsorted.registeredDoctorMap == null) return unsorted;
var sortedMap = Map.fromEntries(
unsorted.registeredDoctorMap!.entries.toList()
..sort((a, b) => a.value!.distance.compareTo(b.value!.distance)),
);
unsorted.registeredDoctorMap = sortedMap;
return unsorted;
}
List<String>? keys = unsorted.registeredDoctorMap?.keys.toList();
keys?.sort();
if (keys == null) return unsorted;
Map<String, PatientDoctorAppointmentListByRegion> sortedMap = {};
for (var key in keys) {
sortedMap[key] = unsorted.registeredDoctorMap![key]!;
}
unsorted.registeredDoctorMap = sortedMap;
return unsorted;
}
static Future<RegionList> getMappedHospitals(
List<HospitalsModel> hospitalList, {
bool isArabic = false,
}) async {
final regionList = RegionList();
final lat = await Utils.getNumFromPrefs(CacheConst.userLat);
final long = await Utils.getNumFromPrefs(CacheConst.userLat);
for (final hospital in hospitalList) {
final region = hospital.getRegionName(isArabic);
if (region == null) continue;
final regionData = regionList.registeredDoctorMap?.putIfAbsent(
region,
() => PatientDoctorAppointmentListByRegion(),
);
List<PatientDoctorAppointmentList>? targetList = hospital.isHMC == true
? regionData?.hmcDoctorList
: regionData?.hmgDoctorList;
List<PatientDoctorAppointmentList> existingEntry = targetList
?.where(
(entry) => entry.filterName == hospital.getName(isArabic),
)
.toList() ??
[];
if (existingEntry.isNotEmpty) {
existingEntry.first.hospitalList.add(hospital);
} else {
final newEntry = PatientDoctorAppointmentList(
filterName: hospital.name,
distanceInKMs: hospital.distanceInKilometers?.toString(),
projectTopName: hospital.name,
projectBottomName: hospital.name,
model: hospital,
isHMC: hospital.isHMC);
final distance = hospital.distanceInKilometers;
if (distance != null) {
if (regionData!.distance > distance) {
regionData.distance = distance;
}
if (hospital.isHMC == true && distance < regionData.hmcDistance) {
regionData.hmcDistance = distance;
} else if (distance < regionData.hmgDistance) {
regionData.hmgDistance = distance;
}
} else if ( lat != 0&&
long != 0 &&
hospital.latitude != null &&
hospital.longitude != null) {
double calculatedDistance = calculateDistance(
lat.toDouble(),
long.toDouble(),
double.parse(hospital.latitude!),
double.parse(hospital.longitude!),
).abs();
if (regionData!.distance > calculatedDistance) {
regionData.distance = calculatedDistance;
}
if (hospital.isHMC == true &&
calculatedDistance < regionData.hmcDistance) {
regionData.hmcDistance = calculatedDistance;
} else if (calculatedDistance < regionData.hmgDistance) {
regionData.hmgDistance = calculatedDistance;
}
}
targetList?.add(newEntry);
}
regionData?.hmcSize = regionData.hmcDoctorList?.length ?? 0;
regionData?.hmgSize = regionData.hmgDoctorList?.length ?? 0;
regionList.registeredDoctorMap?[region] = regionData;
}
return regionList;
}
}

@ -0,0 +1,66 @@
import 'package:flutter/foundation.dart' show ChangeNotifier;
enum AppointmentViaRegionState {
REGION_SELECTION,
TYPE_SELECTION,
HOSPITAL_SELECTION,
CLINIC_SELECTION,
DOCTOR_SELECTION
}
class AppointmentViaRegionViewmodel extends ChangeNotifier {
String? selectedRegionId;
String? selectedFacilityType;
AppointmentViaRegionState bottomSheetState =
AppointmentViaRegionState.REGION_SELECTION;
void setSelectedRegionId(String? regionId) {
selectedRegionId = regionId;
notifyListeners();
}
void setFacility(String? facility) {
selectedFacilityType = facility;
notifyListeners();
}
void setBottomSheetState(AppointmentViaRegionState state) {
bottomSheetState = state;
notifyListeners();
}
void handleBackPress() {
switch (bottomSheetState) {
case AppointmentViaRegionState.REGION_SELECTION:
// Do nothing or exit the bottom sheet
break;
case AppointmentViaRegionState.TYPE_SELECTION:
setBottomSheetState(AppointmentViaRegionState.REGION_SELECTION);
setSelectedRegionId(null);
break;
case AppointmentViaRegionState.HOSPITAL_SELECTION:
setBottomSheetState(AppointmentViaRegionState.TYPE_SELECTION);
break;
// case AppointmentViaRegionState.HOSPITAL_SELECTION:
// case AppointmentViaRegionState.CLINIC_SELECTION:
// setBottomSheetState(AppointmentViaRegionState.TYPE_SELECTION);
// setFacility(null);
// break;
// case AppointmentViaRegionState.DOCTOR_SELECTION:
// if (selectedFacilityType == 'Hospital') {
// setBottomSheetState(AppointmentViaRegionState.HOSPITAL_SELECTION);
// } else if (selectedFacilityType == 'Medical Center') {
// setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION);
// }
// break;
default:
}
}
void flush() {
setSelectedRegionId(null);
setFacility(null);
setBottomSheetState(AppointmentViaRegionState.REGION_SELECTION);
}
}

@ -0,0 +1,8 @@
enum FacilitySelection{
ALL('ALL'),
HMG('hmg'),
HMC('hmc');
final String value;
const FacilitySelection(this.value);
}

@ -0,0 +1,274 @@
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart' show HospitalsModel;
class DoctorList {
int? clinicID;
dynamic appointmentNo;
String? clinicName;
String? doctorTitle;
num? iD;
String? name;
int? projectID;
String? projectName;
int? actualDoctorRate;
num? clinicRoomNo;
dynamic date;
dynamic appointmentDate;
dynamic dayName;
int? doctorID;
String? doctorImageURL;
dynamic doctorProfile;
dynamic doctorProfileInfo;
int? doctorRate;
num? gender;
String? genderDescription;
bool? isAppointmentAllowed;
bool? isDoctorAllowVedioCall;
bool? isDoctorDummy;
bool? isLiveCare;
bool? isLiveCareClinic;
bool? isDoctorHasPrePostImages;
String? latitude;
String? longitude;
String? nationalityFlagURL;
String? nationalityID;
String? nationalityName;
dynamic nearestFreeSlot;
int? noOfPatientsRate;
num? originalClinicID;
num? personRate;
dynamic projectDistanceInKiloMeters;
String? qR;
dynamic qRString;
num? rateNumber;
dynamic serviceID;
String? setupID;
List<String>? speciality;
List<String>? specialityN;
dynamic workingHours;
dynamic decimalDoctorRate;
String? projectBottomName;
String? projectTopName;
bool? isHMC;
String? region;
String? regionArabic;
String? regionEnglish;
String? regionID;
DoctorList(
{this.clinicID,
this.appointmentNo,
this.clinicName,
this.doctorTitle,
this.iD,
this.name,
this.projectID,
this.projectName,
this.actualDoctorRate,
this.clinicRoomNo,
this.date,
this.appointmentDate,
this.dayName,
this.doctorID,
this.doctorImageURL,
this.doctorProfile,
this.doctorProfileInfo,
this.doctorRate,
this.gender,
this.genderDescription,
this.isAppointmentAllowed,
this.isDoctorAllowVedioCall,
this.isDoctorDummy,
this.isLiveCare,
this.isLiveCareClinic,
this.isDoctorHasPrePostImages,
this.latitude,
this.longitude,
this.nationalityFlagURL,
this.nationalityID,
this.nationalityName,
this.nearestFreeSlot,
this.noOfPatientsRate,
this.originalClinicID,
this.personRate,
this.projectDistanceInKiloMeters,
this.qR,
this.qRString,
this.rateNumber,
this.serviceID,
this.setupID,
this.speciality,
this.specialityN,
this.workingHours,
this.decimalDoctorRate,
this.projectBottomName,
this.projectTopName,
this.isHMC,
this.region,
this.regionArabic,
this.regionEnglish,
this.regionID,
});
DoctorList.fromJson(
Map<String, dynamic> json,
) {
clinicID = json['ClinicID'];
appointmentNo = json['AppointmentNo'];
clinicName = json['ClinicName'];
doctorTitle = json['DoctorTitle'];
iD = json['ID'];
name = json['DoctorName'] ?? json['Name'];
projectID = json['ProjectID'];
projectName = json['ProjectName'];
actualDoctorRate = json['ActualDoctorRate'];
clinicRoomNo = json['ClinicRoomNo'];
date = json['Date'];
appointmentDate = json['AppointmentDate'];
dayName = json['DayName'];
doctorID = json['DoctorID'];
doctorImageURL = json['DoctorImageURL'];
doctorProfile = json['DoctorProfile'];
doctorProfileInfo = json['DoctorProfileInfo'];
doctorRate = json['DoctorRate'];
gender = json['Gender'];
genderDescription = json['GenderDescription'];
isAppointmentAllowed = json['IsAppointmentAllowed'];
isDoctorAllowVedioCall = json['IsDoctorAllowVedioCall'];
isDoctorDummy = json['IsDoctorDummy'];
isLiveCare = json['IsLiveCare'];
isLiveCareClinic = json['IsLiveCareClinic'];
isDoctorHasPrePostImages = json['IsDoctorHasPrePostImages'];
latitude = json['Latitude'];
longitude = json['Longitude'];
nationalityFlagURL = json['NationalityFlagURL'];
nationalityID = json['NationalityID'];
nationalityName = json['NationalityName'];
nearestFreeSlot = json['NearestFreeSlot'];
noOfPatientsRate = json['NoOfPatientsRate'];
originalClinicID = json['OriginalClinicID'];
personRate = json['PersonRate'];
projectDistanceInKiloMeters = json['ProjectDistanceInKiloMeters'];
qR = json['QR'];
qRString = json['QRString'];
rateNumber = json['RateNumber'];
serviceID = json['ServiceID'];
setupID = json['SetupID'];
if (json.containsKey('Speciality') && json['Speciality'] != null) speciality = json['Speciality'].cast<String>();
if (json.containsKey('SpecialityN') && json['SpecialityN'] != null) specialityN = json['SpecialityN'].cast<String>();
workingHours = json['WorkingHours'];
decimalDoctorRate = json['DecimalDoctorRate'];
projectBottomName = json['ProjectNameBottom'];
projectTopName = json['ProjectNameTop'];
this.isHMC = json["IsHMC"];
this.regionArabic = json['RegionNameN'];
this.regionEnglish = json['RegionName'];
}
String? getRegionName(bool isArabic) {
if (isArabic) {
return regionArabic;
}
return regionEnglish;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['ClinicID'] = this.clinicID;
data['AppointmentNo'] = this.appointmentNo;
data['ClinicName'] = this.clinicName;
data['DoctorTitle'] = this.doctorTitle;
data['ID'] = this.iD;
data['Name'] = this.name;
data['ProjectID'] = this.projectID;
data['ProjectName'] = this.projectName;
data['ActualDoctorRate'] = this.actualDoctorRate;
data['ClinicRoomNo'] = this.clinicRoomNo;
data['Date'] = this.date;
data['DayName'] = this.dayName;
data['DoctorID'] = this.doctorID;
data['DoctorImageURL'] = this.doctorImageURL;
data['DoctorProfile'] = this.doctorProfile;
data['DoctorProfileInfo'] = this.doctorProfileInfo;
data['DoctorRate'] = this.doctorRate;
data['Gender'] = this.gender;
data['GenderDescription'] = this.genderDescription;
data['IsAppointmentAllowed'] = this.isAppointmentAllowed;
data['IsDoctorAllowVedioCall'] = this.isDoctorAllowVedioCall;
data['IsDoctorDummy'] = this.isDoctorDummy;
data['IsLiveCare'] = this.isLiveCare;
data['IsLiveCareClinic'] = this.isLiveCareClinic;
data['IsDoctorHasPrePostImages'] = this.isDoctorHasPrePostImages;
data['Latitude'] = this.latitude;
data['Longitude'] = this.longitude;
data['NationalityFlagURL'] = this.nationalityFlagURL;
data['NationalityID'] = this.nationalityID;
data['NationalityName'] = this.nationalityName;
data['NearestFreeSlot'] = this.nearestFreeSlot;
data['NoOfPatientsRate'] = this.noOfPatientsRate;
data['OriginalClinicID'] = this.originalClinicID;
data['PersonRate'] = this.personRate;
data['ProjectDistanceInKiloMeters'] = this.projectDistanceInKiloMeters;
data['QR'] = this.qR;
data['QRString'] = this.qRString;
data['RateNumber'] = this.rateNumber;
data['ServiceID'] = this.serviceID;
data['SetupID'] = this.setupID;
data['Speciality'] = this.speciality;
data['SpecialityN'] = this.specialityN;
data['WorkingHours'] = this.workingHours;
data['DecimalDoctorRate'] = this.decimalDoctorRate;
return data;
}
String getProjectCompleteName(){
return "${this.projectTopName} ${this.projectBottomName}";
}
String getProjectCompleteNameWithLocale({bool isArabic = false}) {
if (isArabic) {
return "${this.projectBottomName} ${this.projectTopName}";
}
return "${this.projectTopName} ${this.projectBottomName}";
}
}
class PatientDoctorAppointmentList {
String? filterName = "";
String? distanceInKMs = "";
List<DoctorList>? patientDoctorAppointmentList = [];
String? projectTopName = "";
String? projectBottomName = "";
bool? isHMC;
List<HospitalsModel> hospitalList = [];
PatientDoctorAppointmentList(
{this.filterName,
this.distanceInKMs,
this.projectTopName,
this.projectBottomName,
DoctorList? patientDoctorAppointment,
HospitalsModel? model,
this.isHMC = false}) {
if (model != null) {
hospitalList.add(model);
}
if (patientDoctorAppointment != null) {
patientDoctorAppointmentList!.add(patientDoctorAppointment!);
}
}
}
class PatientDoctorAppointmentListByRegion {
List<PatientDoctorAppointmentList>? hmgDoctorList = [];
List<PatientDoctorAppointmentList>? hmcDoctorList = [];
int hmcSize = 0;
int hmgSize = 0;
num distance = double.infinity;
num hmgDistance = double.infinity;
num hmcDistance = double.infinity;
}
class RegionList {
Map<String, PatientDoctorAppointmentListByRegion?>? registeredDoctorMap = {};
}

@ -0,0 +1,104 @@
class HospitalsModel {
String? desciption;
dynamic desciptionN;
dynamic iD;
String? legalName;
String? legalNameN;
String? name;
dynamic nameN;
String? phoneNumber;
String? setupID;
dynamic distanceInKilometers;
bool? isActive;
String? latitude;
String? longitude;
dynamic mainProjectID;
bool? projectOutSA;
bool? usingInDoctorApp;
bool? isHMC;
String? region;
String? regionArabic;
String? regionEnglish;
String? regionID;
HospitalsModel({
this.desciption,
this.desciptionN,
this.iD,
this.legalName,
this.legalNameN,
this.name,
this.nameN,
this.phoneNumber,
this.setupID,
this.distanceInKilometers,
this.isActive,
this.latitude,
this.longitude,
this.mainProjectID,
this.projectOutSA,
this.usingInDoctorApp,
this.isHMC,
this.region,
this.regionArabic,
this.regionEnglish,
this.regionID,
});
HospitalsModel.fromJson(Map<String, dynamic> json) {
desciption = json['Desciption'];
desciptionN = json['DesciptionN'];
iD = json['ID'];
legalName = json['LegalName'];
legalNameN = json['LegalNameN'];
name = json['Name'];
nameN = json['NameN'];
phoneNumber = json['PhoneNumber'];
setupID = json['SetupID'];
distanceInKilometers = json['DistanceInKilometers'];
isActive = json['IsActive'];
latitude = json['Latitude'];
longitude = json['Longitude'];
mainProjectID = json['MainProjectID'];
projectOutSA = json['ProjectOutSA'];
usingInDoctorApp = json['UsingInDoctorApp'];
this.isHMC = json["IsHMC"];
this.regionArabic = json['RegionNameN'];
this.regionEnglish = json['RegionName'];
}
String? getRegionName(bool isArabic) {
if (isArabic) {
return regionArabic;
}
return regionEnglish;
}
String? getName(bool isArabic) {
if (isArabic) {
return "$nameN";
}
return name;
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['Desciption'] = this.desciption;
data['DesciptionN'] = this.desciptionN;
data['ID'] = this.iD;
data['LegalName'] = this.legalName;
data['LegalNameN'] = this.legalNameN;
data['Name'] = this.name;
data['NameN'] = this.nameN;
data['PhoneNumber'] = this.phoneNumber;
data['SetupID'] = this.setupID;
data['DistanceInKilometers'] = this.distanceInKilometers;
data['IsActive'] = this.isActive;
data['Latitude'] = this.latitude;
data['Longitude'] = this.longitude;
data['MainProjectID'] = this.mainProjectID;
data['ProjectOutSA'] = this.projectOutSA;
data['UsingInDoctorApp'] = this.usingInDoctorApp;
return data;
}
}

@ -3,6 +3,8 @@ 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/api_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.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/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'
show HospitalsModel;
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart';
@ -36,6 +38,9 @@ abstract class MyAppointmentsRepo {
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientAppointmentsForTimeLine(); Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientAppointmentsForTimeLine();
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientDoctorsList(); Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientDoctorsList();
Future<Either<Failure, GenericApiModel<List<HospitalsModel>>>>
getProjectList();
} }
class MyAppointmentsRepoImp implements MyAppointmentsRepo { class MyAppointmentsRepoImp implements MyAppointmentsRepo {
@ -491,4 +496,47 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
return Left(UnknownFailure(e.toString())); return Left(UnknownFailure(e.toString()));
} }
} }
@override
Future<Either<Failure, GenericApiModel<List<HospitalsModel>>>>
getProjectList() async {
Map<String, dynamic> request = {};
try {
GenericApiModel<List<HospitalsModel>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_PROJECT_LIST,
body: request,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['ListProject'];
final appointmentsList = list
.map((item) =>
HospitalsModel.fromJson(item as Map<String, dynamic>))
.toList()
.cast<HospitalsModel>();
apiResponse = GenericApiModel<List<HospitalsModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: appointmentsList,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
} }

@ -1,38 +1,60 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/cache_consts.dart' show CacheConst;
import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils;
import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart'
show RegionList, PatientDoctorAppointmentListByRegion;
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:location/location.dart' show Location;
import '../../core/utils/doctor_response_mapper.dart' show DoctorMapper;
class MyAppointmentsViewModel extends ChangeNotifier { class MyAppointmentsViewModel extends ChangeNotifier {
int selectedTabIndex = 0; int selectedTabIndex = 0;
MyAppointmentsRepo myAppointmentsRepo; MyAppointmentsRepo myAppointmentsRepo;
ErrorHandlerService errorHandlerService; ErrorHandlerService errorHandlerService;
AppState appState;
bool isMyAppointmentsLoading = false; bool isMyAppointmentsLoading = false;
bool isAppointmentPatientShareLoading = false; bool isAppointmentPatientShareLoading = false;
bool isTimeLineAppointmentsLoading = false; bool isTimeLineAppointmentsLoading = false;
bool isPatientMyDoctorsLoading = false; bool isPatientMyDoctorsLoading = false;
List<PatientAppointmentHistoryResponseModel> patientAppointmentsHistoryList = []; List<PatientAppointmentHistoryResponseModel> patientAppointmentsHistoryList =
[];
List<PatientAppointmentHistoryResponseModel> patientUpcomingAppointmentsHistoryList = []; List<PatientAppointmentHistoryResponseModel>
List<PatientAppointmentHistoryResponseModel> patientArrivedAppointmentsHistoryList = []; patientUpcomingAppointmentsHistoryList = [];
List<PatientAppointmentHistoryResponseModel>
patientArrivedAppointmentsHistoryList = [];
List<PatientAppointmentHistoryResponseModel> patientTimelineAppointmentsList = []; List<PatientAppointmentHistoryResponseModel> patientTimelineAppointmentsList =
[];
List<PatientAppointmentHistoryResponseModel> patientMyDoctorsList = []; List<PatientAppointmentHistoryResponseModel> patientMyDoctorsList = [];
PatientAppointmentShareResponseModel? patientAppointmentShareResponseModel; PatientAppointmentShareResponseModel? patientAppointmentShareResponseModel;
MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService}); RegionList? hospitalList;
RegionList? filteredHospitalList;
FacilitySelection currentlySelectedFacility = FacilitySelection.ALL;
bool isRegionListLoading = false;
MyAppointmentsViewModel(
{required this.myAppointmentsRepo, required this.errorHandlerService,required this.appState});
void onTabChange(int index) { void onTabChange(int index) {
selectedTabIndex = index; selectedTabIndex = index;
notifyListeners(); notifyListeners();
} }
initAppointmentsViewModel() { initAppointmentsViewModel() {
patientAppointmentsHistoryList.clear(); patientAppointmentsHistoryList.clear();
patientUpcomingAppointmentsHistoryList.clear(); patientUpcomingAppointmentsHistoryList.clear();
@ -43,6 +65,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
isAppointmentPatientShareLoading = true; isAppointmentPatientShareLoading = true;
isTimeLineAppointmentsLoading = true; isTimeLineAppointmentsLoading = true;
isPatientMyDoctorsLoading = true; isPatientMyDoctorsLoading = true;
isRegionListLoading = true;
notifyListeners(); notifyListeners();
} }
@ -66,7 +89,8 @@ class MyAppointmentsViewModel extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
setAppointmentReminder(bool value, PatientAppointmentHistoryResponseModel item) { setAppointmentReminder(
bool value, PatientAppointmentHistoryResponseModel item) {
int index = patientAppointmentsHistoryList.indexOf(item); int index = patientAppointmentsHistoryList.indexOf(item);
if (index != -1) { if (index != -1) {
patientAppointmentsHistoryList[index].hasReminder = value; patientAppointmentsHistoryList[index].hasReminder = value;
@ -74,12 +98,18 @@ class MyAppointmentsViewModel extends ChangeNotifier {
} }
} }
Future<void> getPatientAppointments(bool isActiveAppointment, bool isArrivedAppointments, {Function(dynamic)? onSuccess, Function(String)? onError}) async { Future<void> getPatientAppointments(
final result = await myAppointmentsRepo.getPatientAppointments(isActiveAppointment: isActiveAppointment, isArrivedAppointments: isArrivedAppointments); bool isActiveAppointment, bool isArrivedAppointments,
final resultArrived = await myAppointmentsRepo.getPatientAppointments(isActiveAppointment: false, isArrivedAppointments: true); {Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myAppointmentsRepo.getPatientAppointments(
isActiveAppointment: isActiveAppointment,
isArrivedAppointments: isArrivedAppointments);
final resultArrived = await myAppointmentsRepo.getPatientAppointments(
isActiveAppointment: false, isArrivedAppointments: true);
result.fold( result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure), (failure) async =>
await errorHandlerService.handleError(failure: failure),
(apiResponse) { (apiResponse) {
if (apiResponse.messageStatus == 2) { if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@ -95,7 +125,8 @@ class MyAppointmentsViewModel extends ChangeNotifier {
); );
resultArrived.fold( resultArrived.fold(
(failure) async => await errorHandlerService.handleError(failure: failure), (failure) async =>
await errorHandlerService.handleError(failure: failure),
(apiResponse) { (apiResponse) {
if (apiResponse.messageStatus == 2) { if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@ -110,19 +141,27 @@ class MyAppointmentsViewModel extends ChangeNotifier {
}, },
); );
patientAppointmentsHistoryList.addAll(patientUpcomingAppointmentsHistoryList); patientAppointmentsHistoryList
patientAppointmentsHistoryList.addAll(patientArrivedAppointmentsHistoryList); .addAll(patientUpcomingAppointmentsHistoryList);
patientAppointmentsHistoryList
.addAll(patientArrivedAppointmentsHistoryList);
print('Upcoming Appointments: ${patientUpcomingAppointmentsHistoryList.length}'); print(
print('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}'); 'Upcoming Appointments: ${patientUpcomingAppointmentsHistoryList.length}');
print(
'Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}');
print('All Appointments: ${patientAppointmentsHistoryList.length}'); print('All Appointments: ${patientAppointmentsHistoryList.length}');
} }
Future<void> getPatientShareAppointment(int projectID, int clinicID, String appointmentNo, {Function(dynamic)? onSuccess, Function(String)? onError}) async { Future<void> getPatientShareAppointment(
final result = await myAppointmentsRepo.getPatientShareAppointment(projectID: projectID, clinicID: clinicID, appointmentNo: appointmentNo); int projectID, int clinicID, String appointmentNo,
{Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myAppointmentsRepo.getPatientShareAppointment(
projectID: projectID, clinicID: clinicID, appointmentNo: appointmentNo);
result.fold( result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure), (failure) async =>
await errorHandlerService.handleError(failure: failure),
(apiResponse) { (apiResponse) {
if (apiResponse.messageStatus == 2) { if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@ -139,11 +178,19 @@ class MyAppointmentsViewModel extends ChangeNotifier {
} }
Future<void> addAdvanceNumberRequest( Future<void> addAdvanceNumberRequest(
{required String advanceNumber, required String paymentReference, required String appointmentNo, Function(dynamic)? onSuccess, Function(String)? onError}) async { {required String advanceNumber,
final result = await myAppointmentsRepo.addAdvanceNumberRequest(advanceNumber: advanceNumber, paymentReference: paymentReference, appointmentNo: appointmentNo); required String paymentReference,
required String appointmentNo,
Function(dynamic)? onSuccess,
Function(String)? onError}) async {
final result = await myAppointmentsRepo.addAdvanceNumberRequest(
advanceNumber: advanceNumber,
paymentReference: paymentReference,
appointmentNo: appointmentNo);
result.fold( result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure), (failure) async =>
await errorHandlerService.handleError(failure: failure),
(apiResponse) { (apiResponse) {
if (apiResponse.messageStatus == 2) { if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@ -158,11 +205,21 @@ class MyAppointmentsViewModel extends ChangeNotifier {
} }
Future<void> generateAppointmentQR( Future<void> generateAppointmentQR(
{required int clinicID, required int projectID, required String appointmentNo, required int isFollowUp, Function(dynamic)? onSuccess, Function(String)? onError}) async { {required int clinicID,
final result = await myAppointmentsRepo.generateAppointmentQR(clinicID: clinicID, projectID: projectID, appointmentNo: appointmentNo, isFollowUp: isFollowUp); required int projectID,
required String appointmentNo,
required int isFollowUp,
Function(dynamic)? onSuccess,
Function(String)? onError}) async {
final result = await myAppointmentsRepo.generateAppointmentQR(
clinicID: clinicID,
projectID: projectID,
appointmentNo: appointmentNo,
isFollowUp: isFollowUp);
result.fold( result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure), (failure) async =>
await errorHandlerService.handleError(failure: failure),
(apiResponse) { (apiResponse) {
if (apiResponse.messageStatus == 2) { if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@ -176,11 +233,18 @@ class MyAppointmentsViewModel extends ChangeNotifier {
); );
} }
Future<void> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { Future<void> cancelAppointment(
final result = await myAppointmentsRepo.cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel); {required PatientAppointmentHistoryResponseModel
patientAppointmentHistoryResponseModel,
Function(dynamic)? onSuccess,
Function(String)? onError}) async {
final result = await myAppointmentsRepo.cancelAppointment(
patientAppointmentHistoryResponseModel:
patientAppointmentHistoryResponseModel);
result.fold( result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure), (failure) async =>
await errorHandlerService.handleError(failure: failure),
(apiResponse) { (apiResponse) {
if (apiResponse.messageStatus == 2) { if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage!); onError!(apiResponse.errorMessage!);
@ -195,11 +259,18 @@ class MyAppointmentsViewModel extends ChangeNotifier {
); );
} }
Future<void> confirmAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { Future<void> confirmAppointment(
final result = await myAppointmentsRepo.confirmAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel); {required PatientAppointmentHistoryResponseModel
patientAppointmentHistoryResponseModel,
Function(dynamic)? onSuccess,
Function(String)? onError}) async {
final result = await myAppointmentsRepo.confirmAppointment(
patientAppointmentHistoryResponseModel:
patientAppointmentHistoryResponseModel);
result.fold( result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure), (failure) async =>
await errorHandlerService.handleError(failure: failure),
(apiResponse) { (apiResponse) {
if (apiResponse.messageStatus == 2) { if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage!); onError!(apiResponse.errorMessage!);
@ -236,7 +307,8 @@ class MyAppointmentsViewModel extends ChangeNotifier {
patientType: patientType); patientType: patientType);
result.fold( result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure), (failure) async =>
await errorHandlerService.handleError(failure: failure),
(apiResponse) { (apiResponse) {
if (apiResponse.messageStatus == 2) { if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@ -251,15 +323,21 @@ class MyAppointmentsViewModel extends ChangeNotifier {
} }
Future<void> sendCheckInNfcRequest( Future<void> sendCheckInNfcRequest(
{required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, {required PatientAppointmentHistoryResponseModel
patientAppointmentHistoryResponseModel,
required String scannedCode, required String scannedCode,
required int checkInType, required int checkInType,
Function(dynamic)? onSuccess, Function(dynamic)? onSuccess,
Function(String)? onError}) async { Function(String)? onError}) async {
final result = await myAppointmentsRepo.sendCheckInNfcRequest(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, scannedCode: scannedCode, checkInType: checkInType); final result = await myAppointmentsRepo.sendCheckInNfcRequest(
patientAppointmentHistoryResponseModel:
patientAppointmentHistoryResponseModel,
scannedCode: scannedCode,
checkInType: checkInType);
result.fold( result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure), (failure) async =>
await errorHandlerService.handleError(failure: failure),
(apiResponse) { (apiResponse) {
if (apiResponse.messageStatus == 2) { if (apiResponse.messageStatus == 2) {
onError!(apiResponse.errorMessage!); onError!(apiResponse.errorMessage!);
@ -274,11 +352,13 @@ class MyAppointmentsViewModel extends ChangeNotifier {
); );
} }
Future<void> getPatientMyDoctors({Function(dynamic)? onSuccess, Function(String)? onError}) async { Future<void> getPatientMyDoctors(
{Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myAppointmentsRepo.getPatientDoctorsList(); final result = await myAppointmentsRepo.getPatientDoctorsList();
result.fold( result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure), (failure) async =>
await errorHandlerService.handleError(failure: failure),
(apiResponse) { (apiResponse) {
if (apiResponse.messageStatus == 2) { if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@ -293,4 +373,84 @@ class MyAppointmentsViewModel extends ChangeNotifier {
}, },
); );
} }
Future<void> getRegionMappedProjectList() async {
if(hospitalList != null && hospitalList!.registeredDoctorMap != null && hospitalList!.registeredDoctorMap!.isNotEmpty){
filteredHospitalList = hospitalList;
return;
}
isRegionListLoading = true;
notifyListeners();
final result = await myAppointmentsRepo.getProjectList();
result.fold(
(failure) async =>
await errorHandlerService.handleError(failure: failure),
(apiResponse) async {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
var projectList = apiResponse.data!;
hospitalList = await DoctorMapper.getMappedHospitals(projectList,
isArabic: false);
var lat = await Utils.getNumFromPrefs(CacheConst.userLat);
var lng = await Utils.getNumFromPrefs(CacheConst.userLong);
var isLocationEnabled = (lat != 0) && (lng != 0);
hospitalList =
await DoctorMapper.sortList(isLocationEnabled, hospitalList!);
isRegionListLoading = false;
filteredHospitalList = hospitalList;
notifyListeners();
}
},
);
}
void setSelectedFacility(FacilitySelection selection) {
currentlySelectedFacility = selection;
notifyListeners();
}
void filterHospitalListByString(String? value, String? selectedRegionId, bool isHMG) {
if(value ==null || value.isEmpty){
filteredHospitalList = hospitalList;
} else {
filteredHospitalList = RegionList();
var list = isHMG
? hospitalList?.registeredDoctorMap![selectedRegionId]!.hmgDoctorList
: hospitalList?.registeredDoctorMap![selectedRegionId]!.hmcDoctorList;
if(list != null && list.isEmpty){ notifyListeners(); return;}
var filteredList = list!.where((element) =>
element.filterName!.toLowerCase().contains(value.toLowerCase())
).toList();
var regionData = PatientDoctorAppointmentListByRegion();
if(isHMG){
regionData.hmgDoctorList = filteredList;
regionData.hmgSize = filteredList.length;
} else {
regionData.hmcDoctorList = filteredList;
regionData.hmcSize = filteredList.length;
}
filteredHospitalList?.registeredDoctorMap = {
selectedRegionId! : regionData
};
}
notifyListeners();
}
Future<bool> isLocationEnabled() async{
return await Location().serviceEnabled();
}
bool getLocationStatus() {
bool isLocationAvaiable = false;
isLocationEnabled().then((value) => isLocationAvaiable = value);
return isLocationAvaiable;
}
} }

@ -815,6 +815,13 @@ abstract class LocaleKeys {
static const ready = 'ready'; static const ready = 'ready';
static const enterValidNationalId = 'enterValidNationalId'; static const enterValidNationalId = 'enterValidNationalId';
static const enterValidPhoneNumber = 'enterValidPhoneNumber'; static const enterValidPhoneNumber = 'enterValidPhoneNumber';
static const medicalCentersWithCount = 'medicalCentersWithCount';
static const medicalCenters = 'medicalCenters';
static const hospitalsWithCount = 'hospitalsWithCount';
static const selectRegion = 'selectRegion';
static const selectFacility = 'selectFacility';
static const selectFacilitiesSubTitle = 'selectFacilitiesSubTitle';
static const selectHospitalSubTitle = 'selectHospitalSubTitle';
static const news = 'news'; static const news = 'news';
} }

@ -14,6 +14,7 @@ import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_mode
import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart';
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart';
@ -110,6 +111,7 @@ void main() async {
create: (_) => MyAppointmentsViewModel( create: (_) => MyAppointmentsViewModel(
myAppointmentsRepo: getIt(), myAppointmentsRepo: getIt(),
errorHandlerService: getIt(), errorHandlerService: getIt(),
appState: getIt(),
), ),
), ),
ChangeNotifierProvider<PayfortViewModel>( ChangeNotifierProvider<PayfortViewModel>(
@ -143,6 +145,8 @@ void main() async {
localAuthService: getIt(), localAuthService: getIt(),
), ),
), ),
ChangeNotifierProvider<AppointmentViaRegionViewmodel>(
create: (_) => AppointmentViaRegionViewmodel())
], child: MyApp()), ], child: MyApp()),
), ),
); );

@ -0,0 +1,88 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.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/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors;
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
class FacilitySelectionItem extends StatelessWidget {
final String svgPath;
final String title;
final String subTitle;
const FacilitySelectionItem(
{super.key,
required this.svgPath,
required this.subTitle,
required this.title});
@override
Widget build(BuildContext context) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Utils.buildSvgWithAssets(
icon: svgPath,
width: 32,
height: 32,
fit: BoxFit.contain,
),
SizedBox(height: 16,),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
info,
Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
iconColor: AppColors.blackColor,
width: 18,
height: 13,
fit: BoxFit.contain,
),
],
)
],
).paddingAll(16.h),
);
}
Widget get info => Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
title,
style: TextStyle(
fontSize: 16.h,
fontWeight: FontWeight.w600,
color: AppColors.blackColor,
),
),
),
Align(
alignment: Alignment.centerLeft,
child: Text(
subTitle,
style: TextStyle(
fontSize: 12.h,
fontWeight: FontWeight.w500,
color: AppColors.greyTextColor,
),
),
),
],
);
}

@ -0,0 +1,79 @@
import 'package:easy_localization/easy_localization.dart'
show tr, StringTranslateExtension;
import 'package:flutter/cupertino.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.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/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'
show MyAppointmentsViewModel;
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/faculity_selection/facility_selection_item.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:provider/provider.dart' show Provider;
class FacilityTypeSelectionWidget extends StatelessWidget {
late MyAppointmentsViewModel myAppointmentsViewModel;
late AppointmentViaRegionViewmodel regionalViewModel;
final String selectedRegion;
FacilityTypeSelectionWidget({super.key, required this.selectedRegion});
@override
Widget build(BuildContext context) {
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context);
regionalViewModel = Provider.of<AppointmentViaRegionViewmodel>(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
LocaleKeys.selectFacility.tr(),
style: TextStyle(
fontSize: 21,
fontWeight: FontWeight.w600,
color: AppColors.blackColor,
),
),
Text(
LocaleKeys.selectFacilitiesSubTitle,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: AppColors.greyTextColor,
),
),
SizedBox(height: 24.h),
FacilitySelectionItem(
svgPath: AppAssets.hmg,
title: "HMG".needTranslation,
subTitle: LocaleKeys.hospitalsWithCount.tr(namedArgs: {
'count':
"${myAppointmentsViewModel.hospitalList?.registeredDoctorMap?[selectedRegion]?.hmgSize ?? 0}"
}),
).onPress(
() {
regionalViewModel.setFacility(FacilitySelection.HMG.name);
regionalViewModel.setBottomSheetState(
AppointmentViaRegionState.HOSPITAL_SELECTION);
},
),
SizedBox(height: 16.h),
FacilitySelectionItem(
svgPath: AppAssets.hmc,
title: "HMC".needTranslation,
subTitle: LocaleKeys.medicalCentersWithCount.tr(namedArgs: {
'count':
"${myAppointmentsViewModel.hospitalList?.registeredDoctorMap?[selectedRegion]?.hmcSize ?? 0}"
})).onPress(
() {
regionalViewModel.setFacility(FacilitySelection.HMC.name);
regionalViewModel.setBottomSheetState(
AppointmentViaRegionState.HOSPITAL_SELECTION);
},
),
],
);
}
}

@ -0,0 +1,109 @@
import 'package:easy_localization/easy_localization.dart'
show tr, StringTranslateExtension;
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/hospital_list_items.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart';
import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors;
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:provider/provider.dart';
class HospitalBottomSheetBody extends StatelessWidget {
late MyAppointmentsViewModel appointmentsViewModel;
late AppointmentViaRegionViewmodel regionalViewModel;
final TextEditingController searchText = TextEditingController();
HospitalBottomSheetBody({super.key});
@override
Widget build(BuildContext context) {
appointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context);
regionalViewModel = Provider.of<AppointmentViaRegionViewmodel>(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
LocaleKeys.selectHospital.tr(),
style: TextStyle(
fontSize: 21,
fontWeight: FontWeight.w600,
color: AppColors.blackColor,
),
),
Text(
LocaleKeys.selectHospitalSubTitle.tr(),
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: AppColors.greyTextColor,
),
),
SizedBox(height: 16.h),
TextInputWidget(
labelText: LocaleKeys.search.tr(),
hintText: "Search Hospital".tr(),
controller: searchText,
onChange: (value) {
appointmentsViewModel.filterHospitalListByString(value, regionalViewModel.selectedRegionId , regionalViewModel.selectedFacilityType ==
FacilitySelection.HMG.name);
},
isEnable: true,
prefix: null,
autoFocus: false,
isBorderAllowed: false,
keyboardType: TextInputType.text,
isAllowLeadingIcon: true,
selectionType: SelectionTypeEnum.search,
padding: EdgeInsets.symmetric(
vertical: ResponsiveExtension(10).h,
horizontal: ResponsiveExtension(15).h,
),
),
SizedBox(height: 24.h),
// TypeSelectionWidget(
// hmcCount: "0",
// hmgCount: "0",
// ),
// SizedBox(height: 21.h),
SizedBox(
height: MediaQuery.sizeOf(context).height * .4,
child: ListView.separated(
itemBuilder: (_, index) => HospitalListItem(
hospitalData: regionalViewModel.selectedFacilityType ==
FacilitySelection.HMG.name
? appointmentsViewModel
.filteredHospitalList!
.registeredDoctorMap![
regionalViewModel.selectedRegionId!]!
.hmgDoctorList![index]
: appointmentsViewModel
.filteredHospitalList
?.registeredDoctorMap?[
regionalViewModel.selectedRegionId!]
?.hmcDoctorList?[index],
isLocationEnabled: appointmentsViewModel.getLocationStatus(),
),
separatorBuilder: (_, __) => SizedBox(
height: 16.h,
),
itemCount: (regionalViewModel.selectedFacilityType ==
FacilitySelection.HMG.name
? (appointmentsViewModel.filteredHospitalList?.registeredDoctorMap?[
regionalViewModel.selectedRegionId]?.hmgDoctorList)
: (appointmentsViewModel
.filteredHospitalList
?.registeredDoctorMap?[
regionalViewModel.selectedRegionId]?.hmcDoctorList))?.length ??
0),
)
],
);
}
}

@ -0,0 +1,113 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.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/my_appointments/models/resp_models/doctor_list_api_response.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
class HospitalListItem extends StatelessWidget {
final PatientDoctorAppointmentList? hospitalData;
final bool isLocationEnabled;
late AppState appState;
HospitalListItem(
{super.key, required this.hospitalData, required this.isLocationEnabled});
@override
Widget build(BuildContext context) {
appState = getIt.get<AppState>();
return DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8.h,
children: [hospitalName, distanceInfo],
),
),
Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
iconColor: AppColors.blackColor,
width: 18,
height: 13,
fit: BoxFit.contain,
),
],
).paddingSymmetrical(16.h, 16.h),
);
}
Widget get hospitalName => Row(
children: [
Utils.buildSvgWithAssets(
icon: (hospitalData?.isHMC == true) ? AppAssets.hmc : AppAssets.hmg,
).paddingOnly(right: 10),
Expanded(
child: Text(
hospitalData?.filterName ?? "",
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
color: AppColors.blackColor,
),
),
)
],
);
Widget get distanceInfo => Row(
children: [
Visibility(
visible: (hospitalData?.distanceInKMs != "0"),
child:
AppCustomChipWidget(
labelText:
"${hospitalData?.distanceInKMs ?? ""} km".needTranslation,
deleteIcon: AppAssets.location_red,
deleteIconSize: Size(9, 12),
backgroundColor: AppColors.secondaryLightRedColor,
textColor: AppColors.errorColor,
),
),
Visibility(
visible: (hospitalData?.distanceInKMs == "0"),
child: Row(
children: [
AppCustomChipWidget(
labelText: "Distance not available".needTranslation,
textColor: AppColors.blackColor,
),
SizedBox(width: 8.h,)
],
)),
Visibility(
visible: !isLocationEnabled,
child: AppCustomChipWidget(
labelText: "Location turned off".needTranslation,
deleteIcon: AppAssets.location_unavailable,
deleteIconSize: Size(9, 12),
textColor: AppColors.blackColor,
)),
],
);
}

@ -0,0 +1,93 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.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/my_appointments/models/facility_selection.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:provider/provider.dart' show Consumer;
class TypeSelectionWidget extends StatelessWidget {
final String hmcCount;
final String hmgCount;
const TypeSelectionWidget(
{super.key, required this.hmcCount, required this.hmgCount});
@override
Widget build(BuildContext context) {
return Consumer<MyAppointmentsViewModel>(
builder: (_, data, __) => Row(
spacing: 8,
mainAxisSize: MainAxisSize.max,
children: [
AppCustomChipWidget(
labelText: "All Facilities".needTranslation,
shape: RoundedRectangleBorder(
side: BorderSide(
color: data.currentlySelectedFacility == FacilitySelection.ALL
? AppColors.errorColor
: AppColors.chipBorderColorOpacity20,
width: 1,
),
borderRadius: BorderRadius.circular(10)),
backgroundColor:
data.currentlySelectedFacility == FacilitySelection.ALL
? AppColors.secondaryLightRedColor
: AppColors.whiteColor,
textColor: data.currentlySelectedFacility == FacilitySelection.ALL
? AppColors.errorColor
: AppColors.blackColor,
).onPress((){
data.setSelectedFacility(FacilitySelection.ALL);
}),
AppCustomChipWidget(
icon: AppAssets.hmg,
iconHasColor: false,
labelText: "Hospitals".needTranslation,
shape: RoundedRectangleBorder(
side: BorderSide(
color: data.currentlySelectedFacility == FacilitySelection.HMG
? AppColors.errorColor
: AppColors.chipBorderColorOpacity20,
width: 1,
),
borderRadius: BorderRadius.circular(10)),
backgroundColor:
data.currentlySelectedFacility == FacilitySelection.HMG
? AppColors.secondaryLightRedColor
: AppColors.whiteColor,
textColor: data.currentlySelectedFacility == FacilitySelection.HMG
? AppColors.errorColor
: AppColors.blackColor,
).onPress((){
data.setSelectedFacility(FacilitySelection.HMG);
}),
AppCustomChipWidget(
icon: AppAssets.hmc,
iconHasColor: false,
labelText: "Medical Centers".needTranslation,
shape: RoundedRectangleBorder(
side: BorderSide(
color: data.currentlySelectedFacility == FacilitySelection.HMC
? AppColors.errorColor
: AppColors.chipBorderColorOpacity20,
width: 1,
),
borderRadius: BorderRadius.circular(10)),
backgroundColor:
data.currentlySelectedFacility == FacilitySelection.HMC
? AppColors.secondaryLightRedColor
: AppColors.whiteColor,
textColor: data.currentlySelectedFacility == FacilitySelection.HMC
? AppColors.errorColor
: AppColors.blackColor,
).onPress((){
data.setSelectedFacility(FacilitySelection.HMC);
}),
],
),
);
}
}

@ -0,0 +1,114 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.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/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors;
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
class RegionListItem extends StatelessWidget {
final String title;
final String hmcCount;
final String hmgCount;
final String subTitle;
const RegionListItem(
{super.key,
required this.title,
required this.subTitle,
required this.hmcCount,
required this.hmgCount});
@override
Widget build(BuildContext context) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: false,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
header,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
spacing: 8.h,
children: [
placesCountItem(
AppAssets.hmg, hmgCount, " ${LocaleKeys.hospital.tr()}"),
placesCountItem(AppAssets.hmc, hmcCount,
" ${LocaleKeys.medicalCenters.tr()}"),
],
),
Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
iconColor: AppColors.blackColor,
width: 18,
height: 13,
fit: BoxFit.contain,
),
],
)
],
).paddingAll(16.h),
);
}
Widget placesCountItem(String svgPath, String count, String title) {
return AppCustomChipWidget(
iconSize: 14,
icon: svgPath,
iconHasColor: false,
richText: RichText(
text: TextSpan(
text: count,
style: TextStyle(
fontSize: 12.h,
fontWeight: FontWeight.w700,
color: AppColors.blackColor),
children: [
TextSpan(
text: title,
style: TextStyle(
fontSize: 12.h,
fontWeight: FontWeight.w500,
color: AppColors.blackColor))
])),
);
}
Widget get header => Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
title,
style: TextStyle(
fontSize: 16.h,
fontWeight: FontWeight.w600,
color: AppColors.blackColor,
),
),
),
Align(
alignment: Alignment.centerLeft,
child: Text(
subTitle,
style: TextStyle(
fontSize: 12.h,
fontWeight: FontWeight.w500,
color: AppColors.greyTextColor,
),
),
),
],
);
}

@ -0,0 +1,86 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils;
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'
show MyAppointmentsViewModel;
import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_item.dart'
show RegionListItem;
import 'package:provider/provider.dart';
class RegionBottomSheetBody extends StatefulWidget {
const RegionBottomSheetBody({super.key});
@override
State<RegionBottomSheetBody> createState() => _RegionBottomSheetBodyState();
}
class _RegionBottomSheetBodyState extends State<RegionBottomSheetBody> {
late MyAppointmentsViewModel myAppointmentsViewModel;
late AppointmentViaRegionViewmodel regionalViewModel;
@override
void initState() {
scheduleMicrotask(() {
myAppointmentsViewModel.getRegionMappedProjectList();
});
super.initState();
}
@override
Widget build(BuildContext context) {
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context);
regionalViewModel = Provider.of<AppointmentViaRegionViewmodel>(context);
return Consumer<MyAppointmentsViewModel>(
builder: (context, myAppointmentsVM, child) {
if (myAppointmentsVM.isRegionListLoading) {
return Container(
height: MediaQuery.of(context).size.height * 0.3,
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
child: Center(
child: Utils.getLoadingWidget(),
),
);
} else {
return SizedBox(
height: MediaQuery.of(context).size.height * 0.5,
child: ListView.separated(
itemCount:
myAppointmentsVM.hospitalList?.registeredDoctorMap?.length ??
0,
separatorBuilder: (_, __) {
return SizedBox(
height: 16.h,
);
},
itemBuilder: (_, index) {
String key = myAppointmentsVM
.hospitalList?.registeredDoctorMap?.keys
.toList()[index] ??
'';
return RegionListItem(
title: key,
subTitle: "",
hmcCount:
"${myAppointmentsVM.hospitalList?.registeredDoctorMap?[key]?.hmcSize ?? 0}",
hmgCount:
"${myAppointmentsVM.hospitalList?.registeredDoctorMap?[key]?.hmgSize ?? 0}",
).onPress(() {
regionalViewModel.setSelectedRegionId(key);
regionalViewModel.setBottomSheetState(AppointmentViaRegionState.TYPE_SELECTION);
});
},
),
);
}
},
);
}
}

@ -10,15 +10,24 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/faculity_selection/facility_type_selection_widget.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/region_bottomsheet/region_list_widget.dart'
show RegionBottomSheetBody;
import 'package:hmg_patient_app_new/presentation/book_appointment/search_doctor_by_name.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/search_doctor_by_name.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart';
import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'
show showCommonBottomSheetWithoutHeight;
import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart';
class BookAppointmentPage extends StatefulWidget { class BookAppointmentPage extends StatefulWidget {
const BookAppointmentPage({super.key}); const BookAppointmentPage({super.key});
@ -28,6 +37,7 @@ class BookAppointmentPage extends StatefulWidget {
class _BookAppointmentPageState extends State<BookAppointmentPage> { class _BookAppointmentPageState extends State<BookAppointmentPage> {
late AppState appState; late AppState appState;
late AppointmentViaRegionViewmodel regionalViewModel;
late BookAppointmentsViewModel bookAppointmentsViewModel; late BookAppointmentsViewModel bookAppointmentsViewModel;
@override @override
@ -42,6 +52,8 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false); bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
appState = getIt.get<AppState>(); appState = getIt.get<AppState>();
regionalViewModel =
Provider.of<AppointmentViaRegionViewmodel>(context, listen: true);
return Scaffold( return Scaffold(
backgroundColor: AppColors.bgScaffoldColor, backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView( body: CollapsingListView(
@ -166,7 +178,9 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
), ),
Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h),
], ],
).onPress(() {}), ).onPress(() {
openRegionListBottomSheet(context);
}),
], ],
), ),
), ),
@ -178,4 +192,45 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
} }
return Container(); return Container();
} }
void openRegionListBottomSheet(BuildContext context) {
// AppointmentViaRegionViewmodel? viewmodel = null;
showCommonBottomSheetWithoutHeight(context,
title: "",
titleWidget: Consumer<AppointmentViaRegionViewmodel>(
builder: (_, data, __) => getTitle(data)),
isDismissible: false,
child: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) {
return getRegionalSelectionWidget(data);
}), callBackFunc: () {
regionalViewModel.flush();
});
}
Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data) {
if (data.bottomSheetState == AppointmentViaRegionState.REGION_SELECTION) {
return RegionBottomSheetBody();
}
if(data.bottomSheetState == AppointmentViaRegionState.TYPE_SELECTION){
return FacilityTypeSelectionWidget(selectedRegion: data.selectedRegionId??"",);
}
if (data.bottomSheetState == AppointmentViaRegionState.HOSPITAL_SELECTION) {
return HospitalBottomSheetBody();
} else {
SizedBox.shrink();
}
return SizedBox.shrink();
}
getTitle(AppointmentViaRegionViewmodel data) {
if (data.selectedRegionId == null) {
return LocaleKeys.selectRegion.tr().toText20(weight: FontWeight.w600);
} else {
return Utils.buildSvgWithAssets(
icon: AppAssets.arrow_back, iconColor: Color(0xff2B353E))
.onPress(() {
data.handleBackPress();
});
}
}
} }

@ -31,6 +31,7 @@ class AppColors {
static const Color textColor = Color(0xFF2E3039); static const Color textColor = Color(0xFF2E3039);
static const Color textColorLight = Color(0xFF5E5E5E); static const Color textColorLight = Color(0xFF5E5E5E);
static const Color borderOnlyColor = Color(0xFF2E3039); static const Color borderOnlyColor = Color(0xFF2E3039);
static const Color chipBorderColorOpacity20 = Color(0x332E3039);
static const Color dividerColor = Color(0xFFD2D2D2); static const Color dividerColor = Color(0xFFD2D2D2);
static const Color warningColorYellow = Color(0xFFF4A308); static const Color warningColorYellow = Color(0xFFF4A308);
static const Color blackBgColor = Color(0xFF2E3039); static const Color blackBgColor = Color(0xFF2E3039);

@ -10,23 +10,38 @@ import 'package:smooth_corner/smooth_corner.dart';
class AppCustomChipWidget extends StatelessWidget { class AppCustomChipWidget extends StatelessWidget {
AppCustomChipWidget({ AppCustomChipWidget({
super.key, super.key,
required this.labelText, this.labelText,
this.textColor = AppColors.textColor, this.textColor = AppColors.textColor,
this.backgroundColor = AppColors.greyColor, this.backgroundColor = AppColors.greyColor,
this.iconSize = 12, this.iconSize = 12,
this.icon = "", this.icon = "",
this.iconColor = AppColors.textColor, this.iconColor = AppColors.textColor,
this.richText,
this.iconHasColor = true,
this.shape,
this.deleteIcon,
this.deleteIconSize = const Size(12, 12),
this.deleteIconColor = AppColors.textColor,
this.deleteIconHasColor = false,
}); });
String? labelText; final String? labelText;
Color? textColor; final Widget? richText;
Color? backgroundColor; final Color? textColor;
num iconSize; final Color? backgroundColor;
String icon; final num iconSize;
Color iconColor; final String icon;
final String? deleteIcon;
final Size? deleteIconSize;
final Color iconColor;
final Color? deleteIconColor;
final bool iconHasColor;
final bool deleteIconHasColor;
final OutlinedBorder? shape;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
print("detected icon: $deleteIcon");
return ChipTheme( return ChipTheme(
data: ChipThemeData( data: ChipThemeData(
padding: EdgeInsets.all(0.0), padding: EdgeInsets.all(0.0),
@ -41,18 +56,55 @@ class AppCustomChipWidget extends StatelessWidget {
), ),
child: icon.isNotEmpty child: icon.isNotEmpty
? Chip( ? Chip(
avatar: icon.isNotEmpty ? Utils.buildSvgWithAssets(icon: icon, width: iconSize.h, height: iconSize.h, iconColor: iconColor) : SizedBox.shrink(), avatar: icon.isNotEmpty
label: labelText!.toText10(weight: FontWeight.w500, letterSpacing: -0.64, color: textColor), ? Utils.buildSvgWithAssets(
icon: icon,
width: iconSize.h,
height: iconSize.h,
iconColor: iconHasColor ? iconColor : null)
: SizedBox.shrink(),
label: richText ??
labelText!.toText10(
weight: FontWeight.w500,
letterSpacing: -0.64,
color: textColor),
padding: EdgeInsets.all(0.0), padding: EdgeInsets.all(0.0),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
labelPadding: EdgeInsets.only(left: -4.h, right: 8.h), labelPadding: EdgeInsets.only(
left: -4.h,
right: deleteIcon?.isNotEmpty == true ? 2.h : 8.h),
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
shape: shape,
deleteIcon: deleteIcon?.isNotEmpty == true
? Utils.buildSvgWithAssets(
icon: deleteIcon!,
width: deleteIconSize!.width!.h,
height: deleteIconSize!.height.h,
iconColor: deleteIconHasColor ? deleteIconColor : null)
: null,
onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null,
) )
: Chip( : Chip(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
label: labelText!.toText10(weight: FontWeight.w500, letterSpacing: -0.64, color: textColor), label: richText ??
labelText!.toText10(
weight: FontWeight.w500,
letterSpacing: -0.64,
color: textColor),
padding: EdgeInsets.all(0.0), padding: EdgeInsets.all(0.0),
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
shape: shape,
labelPadding: EdgeInsets.only(
left: 8.h,
right: deleteIcon?.isNotEmpty == true ? -2.h : 8.h),
deleteIcon: deleteIcon?.isNotEmpty == true
? Utils.buildSvgWithAssets(
icon: deleteIcon!,
width: deleteIconSize!.width.h,
height: deleteIconSize!.height.h,
iconColor: deleteIconHasColor ? deleteIconColor : null)
: null,
onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null,
), ),
); );
} }

@ -109,9 +109,11 @@ void showCommonBottomSheetWithoutHeight(
required Widget child, required Widget child,
required VoidCallback callBackFunc, required VoidCallback callBackFunc,
String title = "", String title = "",
bool isCloseButtonVisible = true,
bool isFullScreen = true, bool isCloseButtonVisible = true,
}) { bool isFullScreen = true,
bool isDismissible = true,
Widget? titleWidget,}) {
showModalBottomSheet<String>( showModalBottomSheet<String>(
sheetAnimationStyle: AnimationStyle( sheetAnimationStyle: AnimationStyle(
duration: Duration(milliseconds: 500), // Custom animation duration duration: Duration(milliseconds: 500), // Custom animation duration
@ -120,6 +122,7 @@ void showCommonBottomSheetWithoutHeight(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
showDragHandle: false, showDragHandle: false,
isDismissible: isDismissible,
backgroundColor: AppColors.bottomSheetBgColor, backgroundColor: AppColors.bottomSheetBgColor,
builder: (BuildContext context) { builder: (BuildContext context) {
return SafeArea( return SafeArea(
@ -135,9 +138,10 @@ void showCommonBottomSheetWithoutHeight(
spacing: 16.h, spacing: 16.h,
children: [ children: [
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
title.toText20(weight: FontWeight.w600).expanded, titleWidget ?? title.toText20(weight: FontWeight.w600),
Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, iconColor: Color(0xff2B353E)).onPress(() { Utils.buildSvgWithAssets(icon: AppAssets.close_bottom_sheet_icon, iconColor: Color(0xff2B353E)).onPress(() {
Navigator.of(context).pop(); Navigator.of(context).pop();
}), }),

@ -141,6 +141,7 @@ class TextInputWidget extends StatelessWidget {
), ),
), ),
if (selectionType == SelectionTypeEnum.calendar) _buildTrailingIcon(context), if (selectionType == SelectionTypeEnum.calendar) _buildTrailingIcon(context),
if (selectionType == SelectionTypeEnum.search) _buildTrailingIconForSearch(context),
], ],
), ),
), ),
@ -250,4 +251,16 @@ class TextInputWidget extends StatelessWidget {
), ),
); );
} }
_buildTrailingIconForSearch(BuildContext context) {
final AppState appState = getIt.get<AppState>();
return Container(
height: 40.h,
width: 40.h,
margin: EdgeInsets.zero,
padding: EdgeInsets.all(8.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(borderRadius: 10.h, color: AppColors.whiteColor),
child: Utils.buildSvgWithAssets(icon: AppAssets.search_icon),
);
}
} }

@ -81,6 +81,8 @@ dependencies:
flutter_swiper_view: ^1.1.8 flutter_swiper_view: ^1.1.8
family_bottom_sheet: ^0.1.0 family_bottom_sheet: ^0.1.0
location: ^8.0.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter

Loading…
Cancel
Save