Merge branch 'refs/heads/master' into dev_sikander

pull/46/head
Sikander Saleem 1 month ago
commit f04f3bcd9c

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

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

@ -813,5 +813,15 @@
"enterValidNationalId": "Please enter a valid national ID or file number",
"enterValidPhoneNumber": "Please enter a valid phone number",
"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",
"news": "News",
"iAcceptThe" : "I Accept the",
"personalDetailsVerification": "Personal Details Verification"
}

@ -56,7 +56,7 @@ var RC_BASE_URL = 'https://rc.hmg.com/';
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
var GET_GEO_ZONES = 'Services/Patients.svc/REST/GeoF_GetAllPoints';
@ -212,14 +212,11 @@ var GET_QR_PARKING = 'Services/SWP.svc/REST/GetQRParkingByID';
//URL to get clinic list
var GET_CLINICS_LIST_URL = "Services/lists.svc/REST/GetClinicCentralized";
var GET_CLINICS_LIST_WRT_HOSPITAL_URL = "Services/Lists.svc/REST/GetClinicFromDoctorSchedule";
var GET_CLINICS_LIST_WRT_HOSPITAL_ID_URL = "Services/Lists.svc/REST/GetClinicFromDoctorSchedule";
//URL to get active appointment list
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
var GET_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/SearchDoctorsByTime";

@ -110,6 +110,8 @@ class AppAssets {
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_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 immediate_service_icon = '$svgBasePath/immediate_service_icon.svg';
static const String no_visit_icon = '$svgBasePath/no_visit_icon.svg';

@ -62,7 +62,7 @@ class AppState {
SelectDeviceByImeiRespModelElement? _selectDeviceByImeiRespModelElement;
void setSelectDeviceByImeiRespModelElement(SelectDeviceByImeiRespModelElement value) {
void setSelectDeviceByImeiRespModelElement(SelectDeviceByImeiRespModelElement? value) {
_selectDeviceByImeiRespModelElement = value;
}
@ -132,4 +132,6 @@ class AppState {
set setUserRegistrationPayload(RegistrationDataModelPayload value) {
_userRegistrationPayload = value;
}
}

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

@ -29,7 +29,7 @@ enum CountryEnum { saudiArabia, unitedArabEmirates }
enum CalenderEnum { gregorian, hijri }
enum SelectionTypeEnum { dropdown, calendar }
enum SelectionTypeEnum { dropdown, calendar, search }
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;
}
}

@ -237,10 +237,15 @@ extension EmailValidator on String {
style: TextStyle(height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.fSize, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
);
Widget toText26({Color? color, bool isBold = false, double? height, bool isCenter = false}) => Text(
Widget toText26({Color? color, bool isBold = false, double? height, bool isCenter = false, FontWeight? weight, double? letterSpacing}) => Text(
this,
textAlign: isCenter ? TextAlign.center : null,
style: TextStyle(height: height ?? 23 / 26, color: color ?? AppColors.blackColor, fontSize: 26.fSize, letterSpacing: -1, fontWeight: isBold ? FontWeight.bold : FontWeight.normal),
style: TextStyle(
height: height ?? 23 / 26,
color: color ?? AppColors.blackColor,
fontSize: 26.fSize,
letterSpacing: letterSpacing ?? -1,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)),
);
Widget toText28({Color? color, bool isBold = false, double? height, bool isCenter = false}) => Text(

@ -1,5 +1,6 @@
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter/material.dart';
@ -36,6 +37,7 @@ import 'package:hmg_patient_app_new/services/localauth_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/bottomsheet/exception_bottom_sheet.dart';
import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart';
import 'models/request_models/get_user_mobile_device_data.dart';
import 'models/request_models/insert_patient_mobile_deviceinfo.dart';
@ -48,6 +50,7 @@ class AuthenticationViewModel extends ChangeNotifier {
final DialogService _dialogService;
final NavigationService _navigationService;
final LocalAuthService _localAuthService;
AuthenticationViewModel({
required AppState appState,
required AuthenticationRepo authenticationRepo,
@ -79,6 +82,8 @@ class AuthenticationViewModel extends ChangeNotifier {
CalenderEnum calenderType = CalenderEnum.gregorian;
LoginTypeEnum loginTypeEnum = LoginTypeEnum.sms;
final ValueNotifier<bool> otpScreenNotifier = ValueNotifier<bool>(false);
//==================
String errorMsg = '';
@ -106,7 +111,6 @@ class AuthenticationViewModel extends ChangeNotifier {
}
Future<void> clearDefaultInputValues() async {
nationalIdController.clear();
phoneNumberController.clear();
emailController.clear();
@ -159,6 +163,10 @@ class AuthenticationViewModel extends ChangeNotifier {
notifyListeners();
}
void clearEmailInput() {
emailController.text = "";
}
void onUAEUserCountrySelection(String? value) {
pickedCountryByUAEUser = countriesList!.firstWhere((element) => element.name == value);
notifyListeners();
@ -355,7 +363,7 @@ class AuthenticationViewModel extends ChangeNotifier {
);
// TODO: GET APP SMS SIGNATURE HERE
request.sMSSignature = "enKTDcqbOVd";
request.sMSSignature =await getSignature();
if (checkIsUserComingForRegister(request: payload)) {
_appState.setUserRegistrationPayload = RegistrationDataModelPayload.fromJson(payload);
@ -394,12 +402,8 @@ class AuthenticationViewModel extends ChangeNotifier {
return isUserComingForRegister;
}
Future<void> checkActivationCode({
required String? activationCode,
required OTPTypeEnum otpTypeEnum,
required Function(String? message) onWrongActivationCode,
Function()? onResendActivation,
}) async {
Future<void> checkActivationCode(
{required String? activationCode, required OTPTypeEnum otpTypeEnum, required Function(String? message) onWrongActivationCode, Function()? onResendActivation}) async {
bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true || _appState.getUserRegistrationPayload.patientOutSa == 1);
final request = RequestUtils.getCommonRequestWelcome(
@ -422,6 +426,7 @@ class AuthenticationViewModel extends ChangeNotifier {
countryCode: _appState.getSelectDeviceByImeiRespModelElement != null && _appState.getSelectDeviceByImeiRespModelElement!.outSa == true
? CountryEnum.unitedArabEmirates.countryCode
: selectedCountrySignup.countryCode,
//TODO: Error Here IN Zip Code.
loginType: loginTypeEnum.toInt)
.toJson();
LoaderBottomSheet.showLoader();
@ -469,6 +474,7 @@ class AuthenticationViewModel extends ChangeNotifier {
failure: failure,
onUnHandledFailure: (failure) async {
LoaderBottomSheet.hideLoader();
otpScreenNotifier.value = true;
await _dialogService.showCommonBottomSheetWithoutH(message: failure.message, label: LocaleKeys.notice.tr(), onOkPressed: () {});
},
onMessageStatusFailure: (failure) async {
@ -595,6 +601,7 @@ class AuthenticationViewModel extends ChangeNotifier {
}
Future<void> onWrongActivationCode({String? message}) async {
otpScreenNotifier.value = true;
await _dialogService.showErrorBottomSheet(message: message ?? "Something went wrong. ", onOkPressed: () {});
}
@ -622,19 +629,20 @@ class AuthenticationViewModel extends ChangeNotifier {
}
checkLastLoginStatus(Function() onSuccess) async {
Future.delayed(Duration(seconds: 1), () async {
if (cacheService.getBool(key: CacheConst.quickLoginEnabled) == null) {
if (_appState.getSelectDeviceByImeiRespModelElement != null &&
(_appState.getSelectDeviceByImeiRespModelElement!.logInType == 1 || _appState.getSelectDeviceByImeiRespModelElement!.logInType == 4)) {
phoneNumberController.text =
(_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") : _appState.getAuthenticatedUser()!.mobileNumber)!;
phoneNumberController.text = (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0")
? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "")
: _appState.getAuthenticatedUser()!.mobileNumber)!;
nationalIdController.text = _appState.getAuthenticatedUser()!.nationalityId!;
onSuccess();
} else if ((loginTypeEnum == LoginTypeEnum.sms || loginTypeEnum == LoginTypeEnum.whatsapp && _appState.getSelectDeviceByImeiRespModelElement == null) &&
_appState.getAuthenticatedUser() != null) {
phoneNumberController.text =
(_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") : _appState.getAuthenticatedUser()!.mobileNumber)!;
phoneNumberController.text = (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0")
? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "")
: _appState.getAuthenticatedUser()!.mobileNumber)!;
nationalIdController.text = _appState.getAuthenticatedUser()!.nationalityId!;
onSuccess();
}
@ -674,21 +682,27 @@ class AuthenticationViewModel extends ChangeNotifier {
}
Future<void> onRegistrationComplete() async {
LoaderBottomSheet.showLoader();
// LoaderBottomSheet.showLoader();
LoadingUtils.showFullScreenLoader(loadingText: "Setting up your medical file.\nMay take a moment.");
var request = RequestUtils.getUserSignupCompletionRequest(fullName: nameController.text, emailAddress: emailController.text, gender: genderType, maritalStatus: maritalStatus);
final resultEither = await _authenticationRepo.registerUser(registrationPayloadDataModelRequest: request);
resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async {
if (apiResponse.data is String) {
//TODO: This Section Need to Be Testing.
LoadingUtils.hideFullScreenLoader();
_dialogService.showExceptionBottomSheet(message: apiResponse.data, onOkPressed: () {}, onCancelPressed: () {});
//TODO: Here We Need to Show a Dialog Of Something in the case of Fail With OK and Cancel and the Display Variable WIll be result.
} else {
print(apiResponse.data as Map<String, dynamic>);
LoadingUtils.hideFullScreenLoader();
if (apiResponse.data["MessageStatus"] == 1) {
LoaderBottomSheet.hideLoader();
LoadingUtils.showFullScreenLoader(isSuccessDialog: true);
//TODO: Here We Need to Show a Dialog Of Something in the case of Success.
await clearDefaultInputValues(); // This will Clear All Default Values Of User.
Future.delayed(Duration(seconds: 1), () {
LoadingUtils.hideFullScreenLoader();
_navigationService.pushAndReplace(AppRoutes.loginScreen);
});
}
}
});
@ -736,6 +750,8 @@ class AuthenticationViewModel extends ChangeNotifier {
} else {
//TODO: Here Hide Loader And Show TOAST
//TODO: if (response['ErrorCode'] == '-986') Toast With OK, And Show response as Output.
LoaderBottomSheet.hideLoader();
_dialogService.showErrorBottomSheet(message: response['ErrorMessage']);
}
}
@ -921,4 +937,12 @@ class AuthenticationViewModel extends ChangeNotifier {
},
);
}
Future<String?> getSignature() async {
if (Platform.isAndroid) {
return await SmsVerification.getAppSignature();
} else {
return null;
}
}
}

@ -7,8 +7,11 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart';
import 'package:sms_otp_auto_verify/sms_otp_auto_verify.dart';
import 'package:provider/provider.dart';
typedef OnDone = void Function(String text);
@ -90,6 +93,7 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
int currentIndex = 0;
List<String> strList = [];
bool hasFocus = false;
AuthenticationViewModel? authVm;
@override
void didUpdateWidget(OTPWidget oldWidget) {
@ -124,6 +128,7 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
@override
void initState() {
super.initState();
authVm = context.read<AuthenticationViewModel>();
focusNode = widget.focusNode ?? FocusNode();
_highlightAnimationController = AnimationController(vsync: this);
_initTextController();
@ -132,12 +137,15 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
widget.controller!.addListener(_controllerListener);
}
focusNode.addListener(_focusListener);
authVm?.otpScreenNotifier.addListener(_onOtpScreenNotifierChanged);
}
void _controllerListener() {
if (mounted == true) {
setState(() {
_initTextController();
text = widget.controller?.text ?? "";
currentIndex = text.length;
});
var onTextChanged = widget.onTextChanged;
if (onTextChanged != null) {
@ -154,6 +162,41 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
}
}
void onWrongOtpClear() {
if (mounted) {
setState(() {
text = "";
currentIndex = 0;
strList.clear();
_calculateStrList();
});
// Clear the controller if it exists
if (widget.controller != null) {
widget.controller!.clear();
}
// Remove focus from the input
if (focusNode.hasFocus) {
focusNode.unfocus();
}
// Optionally refocus after a short delay to allow user to re-enter OTP
Future.delayed(const Duration(milliseconds: 100), () {
if (mounted && widget.autoFocus) {
FocusScope.of(context).requestFocus(focusNode);
}
});
}
}
void _onOtpScreenNotifierChanged() {
if (authVm?.otpScreenNotifier.value == true) {
onWrongOtpClear();
authVm?.otpScreenNotifier.value = false;
}
}
void _initTextController() {
if (widget.controller == null) {
return;
@ -304,19 +347,17 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
);
}
Widget _buildPinCode(int i, BuildContext context) {
Color pinBoxColor;
if (widget.hasError) {
pinBoxColor = widget.errorBorderColor;
} else if (text.length == widget.maxLength) {
// Check for completion first, before individual box logic
pinBoxColor = AppColors.successColor;
} else if (i < text.length) {
pinBoxColor = AppColors.blackBgColor; // Custom color for filled boxes
pinBoxColor = AppColors.blackBgColor;
} else {
pinBoxColor = widget.pinBoxColor; // Default white color
pinBoxColor = widget.pinBoxColor;
}
EdgeInsets insets;
@ -355,7 +396,6 @@ class OTPWidgetState extends State<OTPWidget> with SingleTickerProviderStateMixi
);
}
// Widget _buildPinCode(int i, BuildContext context) {
// Color pinBoxColor = widget.pinBoxColor;
//
@ -455,13 +495,14 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
Timer? _resendTimer;
int _resendTime = 120;
bool _isOtpComplete = false;
bool _isVerifying = false; // Flag to prevent multiple verification calls
bool _isVerifying = false;
@override
void initState() {
super.initState();
_otpController = TextEditingController();
_startResendTimer();
checkSignature();
}
@override
@ -495,6 +536,30 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
}
}
void checkSignature() async {
SmsVerification.startListeningSms().then((message) {
final intRegex = RegExp(r'\d+', multiLine: true);
var otp = SmsVerification.getCode(message, intRegex);
if (otp != null && otp.length == _otpLength) {
autoFillOtp(otp); // Use autoFillOtp to update controller and UI
}
SmsVerification.stopListening();
});
}
void _onAutoOtpChanged(String value) {
setState(() {
_isOtpComplete = value.length == _otpLength;
});
if (_isOtpComplete && !_isVerifying) {
_isVerifying = true;
_verifyOtp(value);
} else if (!_isOtpComplete) {
_isVerifying = false;
}
}
void _resendOtp() {
if (_resendTime == 0) {
setState(() {
@ -504,7 +569,6 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
});
_otpController.clear();
_startResendTimer();
// autoFillOtp("1234");
widget.onResendOTPPressed(widget.phoneNumber);
}
}
@ -618,5 +682,7 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
if (otp.length != _otpLength) return;
_isVerifying = false;
_otpController.text = otp;
setState(() {});
_onOtpChanged(otp); // Ensure verification and color update
}
}

@ -7,6 +7,7 @@ import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctor_profile_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_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/services/logger_service.dart';
@ -40,6 +41,12 @@ abstract class BookAppointmentsRepo {
String? invoiceNoVP,
Function(dynamic)? onSuccess,
Function(String)? onError});
Future<Either<Failure, GenericApiModel<List<HospitalsModel>>>>
getProjectList();
Future<Either<Failure, GenericApiModel<List<GetClinicsListResponseModel>>>> getClinicsWithRespectToClinicId(String projectID);
}
class BookAppointmentsRepoImp implements BookAppointmentsRepo {
@ -362,4 +369,85 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo {
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()));
}
}
@override
Future<Either<Failure, GenericApiModel<List<GetClinicsListResponseModel>>>> getClinicsWithRespectToClinicId(String projectID) async {
Map<String, dynamic> mapDevice = {"ProjectID": projectID};
try {
GenericApiModel<List<GetClinicsListResponseModel>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_CLINICS_LIST_WRT_HOSPITAL_ID_URL,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['ListClinic'];
final clinicsList = list.map((item) => GetClinicsListResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<GetClinicsListResponseModel>();
apiResponse = GenericApiModel<List<GetClinicsListResponseModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: clinicsList,
);
} 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,8 +1,10 @@
import 'package:easy_localization/easy_localization.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';
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/doctor_response_mapper.dart';
import 'package:hmg_patient_app_new/core/utils/loading_utils.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
@ -13,6 +15,8 @@ import 'package:hmg_patient_app_new/features/book_appointments/models/resp_model
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_clinic_list_response_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/timeslots.dart';
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';
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/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
@ -21,6 +25,7 @@ import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
import 'package:location/location.dart' show Location;
class BookAppointmentsViewModel extends ChangeNotifier {
int selectedTabIndex = 0;
@ -60,6 +65,14 @@ class BookAppointmentsViewModel extends ChangeNotifier {
MyAppointmentsViewModel myAppointmentsViewModel;
late AppState _appState;
RegionList? hospitalList;
RegionList? filteredHospitalList;
FacilitySelection currentlySelectedFacility = FacilitySelection.ALL;
bool isRegionListLoading = false;
///this will be used to call the clinic call when navigating from my REGION SELECTION to book appointment screen
bool shouldLoadSpecificClinic = false;
String? currentlySelectedHospitalFromRegionFlow;
BookAppointmentsViewModel({required this.bookAppointmentsRepo, required this.errorHandlerService, required this.navigationService, required this.myAppointmentsViewModel});
@ -100,7 +113,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
clinicsList.clear();
}
isClinicsListLoading = value;
notifyListeners();
// notifyListeners();
}
setSelectedClinic(GetClinicsListResponseModel clinic) {
@ -139,7 +152,18 @@ class BookAppointmentsViewModel extends ChangeNotifier {
notifyListeners();
}
Future<void> getClinics({Function(dynamic)? onSuccess, Function(String)? onError}) async {
/// this function will decide which clinic api to be called
/// either api for region flow or the select clinic api
Future<void> getClinics() async
{
if(shouldLoadSpecificClinic) {
getRegionSelectedClinics();
} else {
getAllClinics();
}
}
Future<void> getAllClinics({Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await bookAppointmentsRepo.getClinics();
result.fold(
@ -164,6 +188,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
Future<void> getDoctorsList(
{int projectID = 0, bool isNearest = false, int doctorId = 0, String doctorName = "", isContinueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
doctorsList.clear();
projectID = currentlySelectedHospitalFromRegionFlow != null?int.parse(currentlySelectedHospitalFromRegionFlow!):projectID;
final result = await bookAppointmentsRepo.getDoctorsList(selectedClinic.clinicID ?? 0, projectID, isNearest, doctorId, doctorName);
result.fold(
@ -380,4 +405,114 @@ class BookAppointmentsViewModel extends ChangeNotifier {
},
);
}
Future<void> getRegionMappedProjectList() async {
//todo handle the case in the location is switch on
if(hospitalList != null && hospitalList!.registeredDoctorMap != null && hospitalList!.registeredDoctorMap!.isNotEmpty){
filteredHospitalList = hospitalList;
return;
}
isRegionListLoading = true;
notifyListeners();
final result = await bookAppointmentsRepo.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;
}
void setLoadSpecificClinic(bool status) {
shouldLoadSpecificClinic = status;
}
void setProjectID(String? mainProjectID) {
currentlySelectedHospitalFromRegionFlow = mainProjectID;
}
Future<void> getRegionSelectedClinics() async{
final result = await bookAppointmentsRepo.getClinicsWithRespectToClinicId(currentlySelectedHospitalFromRegionFlow??"");
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
clinicsList = apiResponse.data!;
isClinicsListLoading = false;
initializeFilteredList();
notifyListeners();
}
},
);
}
void resetFilterList(){
filteredHospitalList = hospitalList;
}
}

@ -9,7 +9,7 @@ import 'package:hmg_patient_app_new/features/insurance/models/resp_models/patien
import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class InsuranceRepo {
Future<Either<Failure, GenericApiModel<List<PatientInsuranceDetailsResponseModel>>>> getPatientInsuranceDetails({required String patientId});
Future<Either<Failure, GenericApiModel<List<PatientInsuranceDetailsResponseModel>>>> getPatientInsuranceDetails();
Future<Either<Failure, GenericApiModel<List<PatientInsuranceCardHistoryResponseModel>>>> getPatientInsuranceCardHistory({required String patientId});
@ -23,7 +23,7 @@ class InsuranceRepoImp implements InsuranceRepo {
InsuranceRepoImp({required this.loggerService, required this.apiClient});
@override
Future<Either<Failure, GenericApiModel<List<PatientInsuranceDetailsResponseModel>>>> getPatientInsuranceDetails({required String patientId}) async {
Future<Either<Failure, GenericApiModel<List<PatientInsuranceDetailsResponseModel>>>> getPatientInsuranceDetails() async {
Map<String, dynamic> mapDevice = {};
try {

@ -49,10 +49,13 @@ class InsuranceViewModel extends ChangeNotifier {
}
Future<void> getPatientInsuranceDetails({Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await insuranceRepo.getPatientInsuranceDetails(patientId: "1231755");
final result = await insuranceRepo.getPatientInsuranceDetails();
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
// (failure) async => await errorHandlerService.handleError(failure: failure),
(failure) async {
isInsuranceLoading = false;
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});

@ -91,13 +91,13 @@ class MedicalFileRepoImp implements MedicalFileRepo {
// throw Exception("lab list is empty");
// }
final vaccinesList = list.map((item) => PatientSickLeavesResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<PatientSickLeavesResponseModel>();
final sickLeavesList = list.map((item) => PatientSickLeavesResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<PatientSickLeavesResponseModel>();
apiResponse = GenericApiModel<List<PatientSickLeavesResponseModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: vaccinesList,
data: sickLeavesList,
);
} catch (e) {
failure = DataParsingFailure(e.toString());

@ -14,7 +14,7 @@ class PatientSickLeavesResponseModel {
num? actualDoctorRate;
String? appointmentDate;
String? clinicName;
double? decimalDoctorRate;
num? decimalDoctorRate;
String? doctorImageURL;
String? doctorName;
num? doctorRate;
@ -32,7 +32,7 @@ class PatientSickLeavesResponseModel {
String? isInOutPatientDescriptionN;
bool? isLiveCareAppointment;
dynamic medicalDirectorApprovedStatus;
int? noOfPatientsRate;
num? noOfPatientsRate;
dynamic patientName;
String? projectName;
String? qR;

@ -0,0 +1,71 @@
import 'package:flutter/foundation.dart' show ChangeNotifier;
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/doctor_list_api_response.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart';
enum AppointmentViaRegionState {
REGION_SELECTION,
TYPE_SELECTION,
HOSPITAL_SELECTION,
CLINIC_SELECTION,
DOCTOR_SELECTION
}
class AppointmentViaRegionViewmodel extends ChangeNotifier {
String? selectedRegionId;
String? selectedFacilityType;
PatientDoctorAppointmentList? selectedHospital;
final NavigationService navigationService;
AppointmentViaRegionState bottomSheetState =
AppointmentViaRegionState.REGION_SELECTION;
AppointmentViaRegionViewmodel({required this.navigationService});
void setSelectedRegionId(String? regionId) {
selectedRegionId = regionId;
notifyListeners();
}
void setFacility(String? facility) {
selectedFacilityType = facility;
notifyListeners();
}
void setBottomSheetState(AppointmentViaRegionState state) {
bottomSheetState = state;
notifyListeners();
}
void handleLastStep(){
navigationService.pop();
navigationService.push(FadePage(
page: SelectClinicPage(),
),);
}
void handleBackPress() {
switch (bottomSheetState) {
case AppointmentViaRegionState.REGION_SELECTION:
break;
case AppointmentViaRegionState.TYPE_SELECTION:
setBottomSheetState(AppointmentViaRegionState.REGION_SELECTION);
setSelectedRegionId(null);
break;
case AppointmentViaRegionState.HOSPITAL_SELECTION:
setBottomSheetState(AppointmentViaRegionState.TYPE_SELECTION);
break;
default:
}
}
void flush() {
setSelectedRegionId(null);
setFacility(null);
setBottomSheetState(AppointmentViaRegionState.REGION_SELECTION);
}
void setHospitalModel(PatientDoctorAppointmentList? hospital) {
selectedHospital = hospital;
}
}

@ -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/common_models/generic_api_model.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_share_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
@ -36,6 +38,8 @@ abstract class MyAppointmentsRepo {
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientAppointmentsForTimeLine();
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientDoctorsList();
}
class MyAppointmentsRepoImp implements MyAppointmentsRepo {

@ -1,14 +1,18 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_state.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/my_appointments_repo.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import '../../core/utils/doctor_response_mapper.dart' show DoctorMapper;
class MyAppointmentsViewModel extends ChangeNotifier {
int selectedTabIndex = 0;
MyAppointmentsRepo myAppointmentsRepo;
ErrorHandlerService errorHandlerService;
AppState appState;
bool isMyAppointmentsLoading = false;
bool isAppointmentPatientShareLoading = false;
@ -26,7 +30,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
PatientAppointmentShareResponseModel? patientAppointmentShareResponseModel;
MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService});
MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService, required this.appState});
void onTabChange(int index) {
selectedTabIndex = index;
@ -278,7 +282,10 @@ class MyAppointmentsViewModel extends ChangeNotifier {
final result = await myAppointmentsRepo.getPatientDoctorsList();
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
// (failure) async => await errorHandlerService.handleError(failure: failure),
(failure) async {
isPatientMyDoctorsLoading = false;
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});

@ -68,7 +68,10 @@ class PrescriptionsViewModel extends ChangeNotifier {
final result = await prescriptionsRepo.getPatientPrescriptionOrders(patientId: "1231755");
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
// (failure) async => await errorHandlerService.handleError(failure: failure),
(failure) async {
isPrescriptionsOrdersLoading = false;
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});

@ -812,9 +812,18 @@ abstract class LocaleKeys {
static const notNow = 'notNow';
static const pendingActivation = 'pendingActivation';
static const awaitingApproval = 'awaitingApproval';
static const news = 'news';
static const ready = 'ready';
static const enterValidNationalId = 'enterValidNationalId';
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 iAcceptThe = 'iAcceptThe';
static const personalDetailsVerification = 'personalDetailsVerification';
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/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/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/payfort/payfort_view_model.dart';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart';
@ -110,6 +111,7 @@ void main() async {
create: (_) => MyAppointmentsViewModel(
myAppointmentsRepo: getIt(),
errorHandlerService: getIt(),
appState: getIt(),
),
),
ChangeNotifierProvider<PayfortViewModel>(
@ -143,6 +145,9 @@ void main() async {
localAuthService: getIt(),
),
),
ChangeNotifierProvider<AppointmentViaRegionViewmodel>(
create: (_) =>
AppointmentViaRegionViewmodel(navigationService: getIt()))
], 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,80 @@
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/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/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 BookAppointmentsViewModel bookAppointmentViewModel;
late AppointmentViaRegionViewmodel regionalViewModel;
final String selectedRegion;
FacilityTypeSelectionWidget({super.key, required this.selectedRegion});
@override
Widget build(BuildContext context) {
bookAppointmentViewModel = Provider.of<BookAppointmentsViewModel>(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':
"${bookAppointmentViewModel.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':
"${bookAppointmentViewModel.hospitalList?.registeredDoctorMap?[selectedRegion]?.hmcSize ?? 0}"
})).onPress(
() {
regionalViewModel.setFacility(FacilitySelection.HMC.name);
regionalViewModel.setBottomSheetState(
AppointmentViaRegionState.HOSPITAL_SELECTION);
},
),
],
);
}
}

@ -0,0 +1,118 @@
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/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/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 BookAppointmentsViewModel appointmentsViewModel;
late AppointmentViaRegionViewmodel regionalViewModel;
final TextEditingController searchText = TextEditingController();
HospitalBottomSheetBody({super.key});
@override
Widget build(BuildContext context) {
appointmentsViewModel = Provider.of<BookAppointmentsViewModel>(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)
{
var hospital = regionalViewModel.selectedFacilityType ==
FacilitySelection.HMG.name
? appointmentsViewModel
.filteredHospitalList!
.registeredDoctorMap![
regionalViewModel.selectedRegionId!]!
.hmgDoctorList![index]
: appointmentsViewModel
.filteredHospitalList
?.registeredDoctorMap?[
regionalViewModel.selectedRegionId!]
?.hmcDoctorList?[index];
return HospitalListItem(
hospitalData: hospital,
isLocationEnabled: appointmentsViewModel.getLocationStatus(),
).onPress(() {
regionalViewModel.setHospitalModel(hospital);
regionalViewModel.setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION);
regionalViewModel.handleLastStep();
});},
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,94 @@
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/book_appointments/book_appointments_view_model.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<BookAppointmentsViewModel>(
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,87 @@
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/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/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 BookAppointmentsViewModel myAppointmentsViewModel;
late AppointmentViaRegionViewmodel regionalViewModel;
@override
void initState() {
scheduleMicrotask(() {
myAppointmentsViewModel.getRegionMappedProjectList();
});
super.initState();
}
@override
Widget build(BuildContext context) {
myAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context);
regionalViewModel = Provider.of<AppointmentViaRegionViewmodel>(context);
return Consumer<BookAppointmentsViewModel>(
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);
});
},
),
);
}
},
);
}
}

@ -4,6 +4,8 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
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/dependencies.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/core/utils/utils.dart';
@ -153,6 +155,7 @@ class LoginScreenState extends State<LoginScreen> {
required TextEditingController? phoneNumberController,
required AuthenticationViewModel authViewModel,
}) {
AppState appState = getIt<AppState>();
context.showBottomSheet(
isScrollControlled: true,
isDismissible: false,
@ -180,6 +183,8 @@ class LoginScreenState extends State<LoginScreen> {
onOkPress: () {
Navigator.of(context).pop();
})) {
Navigator.of(context).pop();
appState.setSelectDeviceByImeiRespModelElement(null);
await authViewModel.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms);
}
},
@ -209,6 +214,8 @@ class LoginScreenState extends State<LoginScreen> {
onOkPress: () {
Navigator.of(context).pop();
})) {
Navigator.of(context).pop();
appState.setSelectDeviceByImeiRespModelElement(null);
await authViewModel.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.whatsapp);
}
},

@ -2,6 +2,8 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
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/dependencies.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/core/utils/utils.dart';
@ -152,14 +154,38 @@ class _RegisterNew extends State<RegisterNew> {
},
),
SizedBox(width: 12.h),
Expanded(
child: Text(
LocaleKeys.iAcceptTermsConditions.tr(),
Row(
children: [
Text(
LocaleKeys.iAcceptThe.tr(),
style: context.dynamicTextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)),
),
GestureDetector(
onTap: () {
// Navigate to terms and conditions page
Navigator.of(context).pushNamed('/terms');
},
child: Text(
LocaleKeys.termsConditoins.tr(),
style: context.dynamicTextStyle(
fontSize: 14.fSize,
fontWeight: FontWeight.w500,
color: AppColors.primaryRedColor,
decoration: TextDecoration.underline,
decorationColor: AppColors.primaryRedBorderColor,
),
),
),
],
),
// Expanded(
// child: Text(
// LocaleKeys.iAcceptTermsConditions.tr().split("the").first,
// style: context.dynamicTextStyle(fontSize: 14.fSize, fontWeight: FontWeight.w500, color: Color(0xFF2E3039)),
// ),
// ),
],
),
),
SizedBox(height: 25.h),
CustomButton(
@ -224,6 +250,7 @@ class _RegisterNew extends State<RegisterNew> {
}
void showRegisterModel({required BuildContext context, required AuthenticationViewModel authVM}) {
AppState appState = getIt.get<AppState>();
showModalBottomSheet(
context: context,
isScrollControlled: true,
@ -235,7 +262,7 @@ class _RegisterNew extends State<RegisterNew> {
child: SingleChildScrollView(
child: GenericBottomSheet(
countryCode: authVM.selectedCountrySignup.countryCode,
initialPhoneNumber: "",
initialPhoneNumber: authVM.phoneNumberController.text,
textController: authVM.phoneNumberController,
isEnableCountryDropdown: false,
onCountryChange: authVM.onCountryChange,
@ -256,6 +283,7 @@ class _RegisterNew extends State<RegisterNew> {
Navigator.of(context).pop();
},
)) {
appState.setSelectDeviceByImeiRespModelElement(null);
await authVM.onRegistrationStart(otpTypeEnum: OTPTypeEnum.sms);
}
},
@ -280,15 +308,14 @@ class _RegisterNew extends State<RegisterNew> {
child: CustomButton(
text: LocaleKeys.sendOTPWHATSAPP.tr(),
onPressed: () async {
// Dismiss keyboard before validation
FocusScope.of(context).unfocus();
if (ValidationUtils.isValidatePhone(
phoneNumber: authVM.phoneNumberController.text,
onOkPress: () {
Navigator.of(context).pop();
},
)) {
appState.setSelectDeviceByImeiRespModelElement(null);
await authVM.onRegistrationStart(otpTypeEnum: OTPTypeEnum.whatsapp);
}
},
@ -296,6 +323,7 @@ class _RegisterNew extends State<RegisterNew> {
borderColor: AppColors.borderOnlyColor,
textColor: AppColors.textColor,
icon: AppAssets.whatsapp,
iconColor: null,
),
),
],

@ -44,20 +44,24 @@ class _RegisterNew extends State<RegisterNewStep2> {
Widget build(BuildContext context) {
AppState appState = getIt.get<AppState>();
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
appBar: CustomAppBar(
onBackPressed: () {
Navigator.of(context).pop();
// authVM!.clearDefaultInputValues();
authVM!.clearEmailInput();
},
onLanguageChanged: (lang) {},
hideLogoAndLang: true,
),
body: SingleChildScrollView(
reverse: false,
padding: EdgeInsets.only(left: 24.h, right: 24.h, top: 24.h),
padding: EdgeInsets.only(left: 24.h, right: 24.h, top: 0.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
LocaleKeys.personalDetailsVerification.tr().toText26(color: AppColors.textColor, weight: FontWeight.w600, letterSpacing: -2),
SizedBox(height: 24.h),
Directionality(
textDirection: Directionality.of(context),
child: Container(
@ -67,7 +71,8 @@ class _RegisterNew extends State<RegisterNewStep2> {
children: [
TextInputWidget(
labelText: authVM!.isUserFromUAE() ? LocaleKeys.fullName.tr() : LocaleKeys.name.tr(),
hintText: authVM!.isUserFromUAE() ? LocaleKeys.enterNameHere.tr() : ("${appState.getNHICUserData.firstNameEn!.toUpperCase()} ${appState.getNHICUserData.lastNameEn!.toUpperCase()}"),
hintText:
authVM!.isUserFromUAE() ? LocaleKeys.enterNameHere.tr() : ("${appState.getNHICUserData.firstNameEn!.toUpperCase()} ${appState.getNHICUserData.lastNameEn!.toUpperCase()}"),
controller: authVM!.isUserFromUAE() ? authVM!.nameController : null,
isEnable: true,
prefix: null,
@ -76,8 +81,9 @@ class _RegisterNew extends State<RegisterNewStep2> {
keyboardType: TextInputType.text,
isAllowLeadingIcon: true,
isReadOnly: authVM!.isUserFromUAE() ? false : true,
leadingIcon: AppAssets.user_circle)
.paddingSymmetrical(0.h, 16.h),
leadingIcon: AppAssets.user_circle,
labelColor: AppColors.textColor,
).paddingSymmetrical(0.h, 16.h),
Divider(height: 1, color: AppColors.greyColor),
TextInputWidget(
labelText: LocaleKeys.nationalIdNumber.tr(),
@ -89,6 +95,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
isBorderAllowed: false,
isAllowLeadingIcon: true,
isReadOnly: true,
labelColor: AppColors.textColor,
leadingIcon: AppAssets.student_card)
.paddingSymmetrical(0.h, 16.h),
Divider(height: 1, color: AppColors.greyColor),
@ -108,6 +115,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
isBorderAllowed: false,
hasSelectionCustomIcon: true,
isAllowRadius: false,
labelColor: AppColors.textColor,
padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0),
selectionCustomIcon: AppAssets.arrow_down,
leadingIcon: AppAssets.user_full,
@ -124,6 +132,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
isAllowLeadingIcon: true,
isReadOnly: authVM!.isUserFromUAE() ? false : true,
leadingIcon: AppAssets.user_full,
labelColor: AppColors.textColor,
onChange: (value) {})
.paddingSymmetrical(0.h, 16.h),
Divider(height: 1, color: AppColors.greyColor),
@ -143,6 +152,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
isBorderAllowed: false,
hasSelectionCustomIcon: true,
isAllowRadius: false,
labelColor: AppColors.textColor,
padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0),
selectionCustomIcon: AppAssets.arrow_down,
leadingIcon: AppAssets.smart_phone,
@ -160,6 +170,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
isBorderAllowed: false,
isAllowLeadingIcon: true,
isReadOnly: true,
labelColor: AppColors.textColor,
leadingIcon: AppAssets.smart_phone,
onChange: (value) {})
.paddingSymmetrical(0.h, 16.h),
@ -190,6 +201,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
onChange: authVM.onUAEUserCountrySelection,
isBorderAllowed: false,
hasSelectionCustomIcon: true,
labelColor: AppColors.textColor,
isAllowRadius: false,
padding: const EdgeInsets.only(top: 8, bottom: 8, left: 0, right: 0),
selectionCustomIcon: AppAssets.arrow_down,
@ -208,6 +220,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
isBorderAllowed: false,
isAllowLeadingIcon: true,
isReadOnly: true,
labelColor: AppColors.textColor,
leadingIcon: AppAssets.globe,
onChange: (value) {})
.paddingSymmetrical(0.h, 16.h),
@ -224,6 +237,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
isAllowRadius: false,
isBorderAllowed: false,
isAllowLeadingIcon: true,
labelColor: AppColors.textColor,
isReadOnly: true,
leadingIcon: AppAssets.call)
.paddingSymmetrical(0.h, 16.h),
@ -240,6 +254,7 @@ class _RegisterNew extends State<RegisterNewStep2> {
isBorderAllowed: false,
isAllowLeadingIcon: true,
isReadOnly: true,
labelColor: AppColors.textColor,
leadingIcon: AppAssets.birthday_cake,
selectionType: null,
).paddingSymmetrical(0.h, 16.h),
@ -269,12 +284,12 @@ class _RegisterNew extends State<RegisterNewStep2> {
),
Expanded(
child: CustomButton(
backgroundColor: AppColors.lightGreenColor,
borderColor: AppColors.lightGreenColor,
textColor: AppColors.textGreenColor,
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
text: LocaleKeys.confirm.tr(),
icon: AppAssets.confirm,
iconColor: AppColors.textGreenColor,
iconColor: AppColors.whiteColor,
onPressed: () {
if (appState.getUserRegistrationPayload.zipCode != CountryEnum.saudiArabia.countryCode) {
if (ValidationUtils.validateUaeRegistration(

@ -12,6 +12,8 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/authentication/login.dart';
import 'package:hmg_patient_app_new/presentation/home/landing_page.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/app_bar_widget.dart';
import 'package:hmg_patient_app_new/widgets/bottomsheet/generic_bottom_sheet.dart';
@ -171,10 +173,9 @@ class _SavedLogin extends State<SavedLogin> {
authVm.checkUserAuthentication(otpTypeEnum: OTPTypeEnum.sms);
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedBorderColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
icon: AppAssets.sms,
),
icon: AppAssets.sms),
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
@ -247,13 +248,13 @@ class _SavedLogin extends State<SavedLogin> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Container(
child: SizedBox(
height: 56,
child: CustomButton(
text: LocaleKeys.guest.tr(),
onPressed: () {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (BuildContext context) => LoginScreen()),
MaterialPageRoute(builder: (BuildContext context) => LandingNavigation()),
);
},
backgroundColor: Color(0xffFEE9EA),
@ -277,7 +278,7 @@ class _SavedLogin extends State<SavedLogin> {
text: LocaleKeys.switchAccount.tr(),
onPressed: () async {
await authVm.clearDefaultInputValues();
Navigator.of(context).pushReplacement(
Navigator.of(context).push(
MaterialPageRoute(builder: (BuildContext context) => LoginScreen()),
);
},

@ -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/widget_extensions.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/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/select_clinic_page.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/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/transitions/fade_page.dart';
import 'package:provider/provider.dart';
import '../appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart';
class BookAppointmentPage extends StatefulWidget {
const BookAppointmentPage({super.key});
@ -28,6 +37,7 @@ class BookAppointmentPage extends StatefulWidget {
class _BookAppointmentPageState extends State<BookAppointmentPage> {
late AppState appState;
late AppointmentViaRegionViewmodel regionalViewModel;
late BookAppointmentsViewModel bookAppointmentsViewModel;
@override
@ -42,6 +52,8 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
Widget build(BuildContext context) {
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
appState = getIt.get<AppState>();
regionalViewModel =
Provider.of<AppointmentViaRegionViewmodel>(context, listen: true);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: CollapsingListView(
@ -110,6 +122,8 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
],
).onPress(() {
bookAppointmentsViewModel.setIsClinicsListLoading(true);
bookAppointmentsViewModel.setLoadSpecificClinic(false);
bookAppointmentsViewModel.setProjectID(null);
Navigator.of(context).push(
FadePage(
page: SelectClinicPage(),
@ -166,7 +180,9 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
),
Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h),
],
).onPress(() {}),
).onPress(() {
openRegionListBottomSheet(context);
}),
],
),
),
@ -178,4 +194,54 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
}
return Container();
}
void openRegionListBottomSheet(BuildContext context) {
regionalViewModel.flush();
// AppointmentViaRegionViewmodel? viewmodel = null;
showCommonBottomSheetWithoutHeight(context,
title: "",
titleWidget: Consumer<AppointmentViaRegionViewmodel>(
builder: (_, data, __) => getTitle(data)),
isDismissible: false,
child: Consumer<AppointmentViaRegionViewmodel>(builder: (_, data, __) {
return getRegionalSelectionWidget(data);
}), callBackFunc: () {
});
}
Widget getRegionalSelectionWidget(AppointmentViaRegionViewmodel data) {
if (data.bottomSheetState == AppointmentViaRegionState.REGION_SELECTION) {
return RegionBottomSheetBody();
}
if(data.bottomSheetState == AppointmentViaRegionState.TYPE_SELECTION){
bookAppointmentsViewModel.resetFilterList();
return FacilityTypeSelectionWidget(selectedRegion: data.selectedRegionId??"",);
}
if (data.bottomSheetState == AppointmentViaRegionState.HOSPITAL_SELECTION) {
return HospitalBottomSheetBody();
}
if (data.bottomSheetState == AppointmentViaRegionState.CLINIC_SELECTION) {
// Navigator.of(context).pop();
bookAppointmentsViewModel.setIsClinicsListLoading(true);
bookAppointmentsViewModel.setLoadSpecificClinic(true);
bookAppointmentsViewModel.setProjectID(regionalViewModel.selectedHospital?.hospitalList.first?.mainProjectID.toString());
}
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();
});
}
}
}

@ -70,6 +70,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
insuranceViewModel.initInsuranceProvider();
medicalFileViewModel.setIsPatientSickLeaveListLoading(true);
medicalFileViewModel.getPatientSickLeaveList();
medicalFileViewModel.onTabChanged(0);
}
});
super.initState();
@ -647,9 +648,14 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
Consumer<InsuranceViewModel>(builder: (context, insuranceVM, child) {
return insuranceVM.isInsuranceLoading
? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.0)
: PatientInsuranceCard(
: insuranceVM.patientInsuranceList.isNotEmpty
? PatientInsuranceCard(
insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first,
isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo)));
isInsuranceExpired: DateTime.now().isAfter(
DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo),
),
)
: SizedBox.shrink();
}),
SizedBox(height: 10.h),
GridView(

@ -22,19 +22,9 @@ class NavigationService {
navigatorKey.currentState?.pushReplacementNamed(routeName);
}
Future<T?> pushToOtpScreen<T>({
required String phoneNumber,
required Function(int code) checkActivationCode,
required Function(String phoneNumber) onResendOTPPressed,
}) {
Future<T?> pushToOtpScreen<T>({required String phoneNumber, required Function(int code) checkActivationCode, required Function(String phoneNumber) onResendOTPPressed}) {
return navigatorKey.currentState!.push(
MaterialPageRoute(
builder: (_) => OTPVerificationScreen(
phoneNumber: phoneNumber,
checkActivationCode: checkActivationCode,
onResendOTPPressed: onResendOTPPressed,
),
),
MaterialPageRoute(builder: (_) => OTPVerificationScreen(phoneNumber: phoneNumber, checkActivationCode: checkActivationCode, onResendOTPPressed: onResendOTPPressed)),
);
}

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

@ -10,20 +10,34 @@ import 'package:smooth_corner/smooth_corner.dart';
class AppCustomChipWidget extends StatelessWidget {
AppCustomChipWidget({
super.key,
required this.labelText,
this.labelText,
this.textColor = AppColors.textColor,
this.backgroundColor = AppColors.greyColor,
this.iconSize = 12,
this.icon = "",
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;
Color? textColor;
Color? backgroundColor;
num iconSize;
String icon;
Color iconColor;
final String? labelText;
final Widget? richText;
final Color? textColor;
final Color? backgroundColor;
final num iconSize;
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
Widget build(BuildContext context) {
@ -41,18 +55,55 @@ class AppCustomChipWidget extends StatelessWidget {
),
child: icon.isNotEmpty
? Chip(
avatar: icon.isNotEmpty ? Utils.buildSvgWithAssets(icon: icon, width: iconSize.h, height: iconSize.h, iconColor: iconColor) : SizedBox.shrink(),
label: labelText!.toText10(weight: FontWeight.w500, letterSpacing: -0.64, color: textColor),
avatar: icon.isNotEmpty
? 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),
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,
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(
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),
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 VoidCallback callBackFunc,
String title = "",
bool isCloseButtonVisible = true,
bool isFullScreen = true,
}) {
bool isDismissible = true,
Widget? titleWidget,}) {
showModalBottomSheet<String>(
sheetAnimationStyle: AnimationStyle(
duration: Duration(milliseconds: 500), // Custom animation duration
@ -120,6 +122,7 @@ void showCommonBottomSheetWithoutHeight(
context: context,
isScrollControlled: true,
showDragHandle: false,
isDismissible: isDismissible,
backgroundColor: AppColors.bottomSheetBgColor,
builder: (BuildContext context) {
return SafeArea(
@ -135,9 +138,10 @@ void showCommonBottomSheetWithoutHeight(
spacing: 16.h,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
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(() {
Navigator.of(context).pop();
}),

@ -18,9 +18,10 @@ class DropdownWidget extends StatelessWidget {
final bool hasSelectionCustomIcon;
final String? selectionCustomIcon;
final String? leadingIcon;
final Color? labelColor;
const DropdownWidget({
Key? key,
const DropdownWidget(
{Key? key,
required this.labelText,
required this.hintText,
required this.dropdownItems,
@ -33,14 +34,15 @@ class DropdownWidget extends StatelessWidget {
this.hasSelectionCustomIcon = false,
this.selectionCustomIcon,
this.leadingIcon,
}) : super(key: key);
this.labelColor})
: super(key: key);
@override
Widget build(BuildContext context) {
Widget content = Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [_buildLabelText(), _buildDropdown(context)],
children: [_buildLabelText(labelColor), _buildDropdown(context)],
);
return Container(
@ -75,13 +77,13 @@ class DropdownWidget extends StatelessWidget {
child: Utils.buildSvgWithAssets(icon: leadingIcon!));
}
Widget _buildLabelText() {
Widget _buildLabelText(Color? labelColor) {
return Text(
labelText,
style: TextStyle(
fontSize: 12.fSize,
fontWeight: FontWeight.w500,
color: Color(0xff898A8D),
color: labelColor ?? Color(0xff898A8D),
letterSpacing: -0.2,
height: 18 / 12,
),

@ -41,12 +41,13 @@ class TextInputWidget extends StatelessWidget {
final num? fontSize;
final bool? isWalletAmountInput;
final Widget? suffix;
final Color? labelColor;
// final List<Country> countryList;
// final Function(Country)? onCountryChange;
TextInputWidget({
super.key,
TextInputWidget(
{super.key,
required this.labelText,
required this.hintText,
this.controller,
@ -71,6 +72,7 @@ class TextInputWidget extends StatelessWidget {
this.fontSize = 14,
this.isWalletAmountInput = false,
this.suffix,
this.labelColor
// this.countryList = const [],
// this.onCountryChange,
});
@ -135,12 +137,13 @@ class TextInputWidget extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildLabelText().paddingOnly(right: (appState.getLanguageCode() == "ar" ? 10 : 0)),
_buildLabelText(labelColor).paddingOnly(right: (appState.getLanguageCode() == "ar" ? 10 : 0)),
_buildTextField(context),
],
),
),
if (selectionType == SelectionTypeEnum.calendar) _buildTrailingIcon(context),
if (selectionType == SelectionTypeEnum.search) _buildTrailingIconForSearch(context),
],
),
),
@ -205,13 +208,13 @@ class TextInputWidget extends StatelessWidget {
);
}
Widget _buildLabelText() {
Widget _buildLabelText(Color? labelColor) {
return Text(
labelText,
style: TextStyle(
fontSize: 12.fSize,
fontWeight: FontWeight.w500,
color: AppColors.inputLabelTextColor,
color: labelColor ?? AppColors.inputLabelTextColor,
letterSpacing: -0.2,
height: 18 / 12,
),
@ -250,4 +253,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),
);
}
}

@ -66,6 +66,7 @@ dependencies:
firebase_analytics: ^11.5.1
jiffy: ^6.4.3
hijri_gregorian_calendar: ^0.1.1
sms_otp_auto_verify: ^2.2.0
web: any
flutter_staggered_animations: ^1.1.1
@ -81,6 +82,8 @@ dependencies:
flutter_swiper_view: ^1.1.8
family_bottom_sheet: ^0.1.0
location: ^8.0.1
dev_dependencies:
flutter_test:
sdk: flutter

Loading…
Cancel
Save